预计阅读本页时间:-
Test Your Knowledge: Part VI Exercises
These exercises ask you to write a few classes and experiment with some existing code. Of course, the problem with existing code is that it must be existing. To work with the set class in exercise 5, either pull the class source code off this book’s website (see the Preface for a pointer) or type it up by hand (it’s fairly brief). These programs are starting to get more sophisticated, so be sure to check the solutions at the end of the book for pointers. You’ll find them in Appendix B, under Part VI, Classes and OOP.
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
- Inheritance. Write a class called Adder that exports a method add(self, x, y) that prints a “Not Implemented” message. Then, define two subclasses of Adder that implement the add method:
ListAdder
With an add method that returns the concatenation of its two list arguments
DictAdder
With an add method that returns a new dictionary containing the items in both its two dictionary arguments (any definition of addition will do)
Experiment by making instances of all three of your classes interactively and calling their add methods.
Now, extend your Adder superclass to save an object in the instance with a constructor (e.g., assign self.data a list or a dictionary), and overload the + operator with an __add__ method to automatically dispatch to your add methods (e.g., X + Y triggers X.add(X.data,Y)). Where is the best place to put the constructors and operator overloading methods (i.e., in which classes)? What sorts of objects can you add to your class instances?
In practice, you might find it easier to code your add methods to accept just one real argument (e.g., add(self,y)), and add that one argument to the instance’s current data (e.g., self.data + y). Does this make more sense than passing two arguments to add? Would you say this makes your classes more “object-oriented”? - Operator overloading. Write a class called MyList that shadows (“wraps”) a Python list: it should overload most list operators and operations, including +, indexing, iteration, slicing, and list methods such as append and sort. See the Python reference manual for a list of all possible methods to support. Also, provide a constructor for your class that takes an existing list (or a MyList instance) and copies its components into an instance member. Experiment with your class interactively. Things to explore:
- Why is copying the initial value important here?
- Can you use an empty slice (e.g., start[:]) to copy the initial value if it’s a MyList instance?
- Is there a general way to route list method calls to the wrapped list?
- Can you add a MyList and a regular list? How about a list and a MyList instance?
- What type of object should operations like + and slicing return? What about indexing operations?
- If you are working with a more recent Python release (version 2.2 or later), you may implement this sort of wrapper class by embedding a real list in a standalone class, or by extending the built-in list type with a subclass. Which is easier, and why?
- Subclassing. Make a subclass of MyList from exercise 2 called MyListSub, which extends MyList to print a message to stdout before each overloaded operation is called and counts the number of calls. MyListSub should inherit basic method behavior from MyList. Adding a sequence to a MyListSub should print a message, increment the counter for + calls, and perform the superclass’s method. Also, introduce a new method that prints the operation counters to stdout, and experiment with your class interactively. Do your counters count calls per instance, or per class (for all instances of the class)? How would you program the other option)? (Hint: it depends on which object the count members are assigned to: class members are shared by instances, but self members are per-instance data.)
- Attribute methods. Write a class called Meta with methods that intercept every attribute qualification (both fetches and assignments), and print messages listing their arguments to stdout. Create a Meta instance, and experiment with qualifying it interactively. What happens when you try to use the instance in expressions? Try adding, indexing, and slicing the instance of your class. (Note: a fully generic approach based upon __getattr__ will work in 2.6 but not 3.0, for reasons noted in Chapter 30 and restated in the solution to this exercise.)
- Set objects. Experiment with the set class described in Extending Types by Embedding. Run commands to do the following sorts of operations:
- Create two sets of integers, and compute their intersection and union by using & and | operator expressions.
- Create a set from a string, and experiment with indexing your set. Which methods in the class are called?
- Try iterating through the items in your string set using a for loop. Which methods run this time?
- Try computing the intersection and union of your string set and a simple Python string. Does it work?
- Now, extend your set by subclassing to handle arbitrarily many operands using the *args argument form. (Hint: see the function versions of these algorithms in Chapter 18.) Compute intersections and unions of multiple operands with your set subclass. How can you intersect three or more sets, given that & has only two sides?
- How would you go about emulating other list operations in the set class? (Hint: __add__ can catch concatenation, and __getattr__ can pass most list method calls to the wrapped list.)
- Class tree links. In Namespaces: The Whole Story in Chapter 28 and in Multiple Inheritance: “Mix-in” Classes in Chapter 30, I mentioned that classes have a __bases__ attribute that returns a tuple of their superclass objects (the ones listed in parentheses in the class header). Use __bases__ to extend the lister.py mix-in classes we wrote in Chapter 30 so that they print the names of the immediate superclasses of the instance’s class. When you’re done, the first line of the string representation should look like this (your address may vary):
<Instance of Sub(Super, Lister), address 7841200:
- Composition. Simulate a fast-food ordering scenario by defining four classes:
Lunch
A container and controller class
Customer
The actor who buys food
Employee
The actor from whom a customer orders
Food
What the customer buys
To get you started, here are the classes and methods you’ll be defining:class Lunch:
The order simulation should work as follows:
def __init__(self) # Make/embed Customer and Employee
def order(self, foodName) # Start a Customer order simulation
def result(self) # Ask the Customer what Food it has
class Customer:
def __init__(self) # Initialize my food to None
def placeOrder(self, foodName, employee) # Place order with an Employee
def printFood(self) # Print the name of my food
class Employee:
def takeOrder(self, foodName) # Return a Food, with requested name
class Food:
def __init__(self, name) # Store food name- The Lunch class’s constructor should make and embed an instance of Customer and an instance of Employee, and it should export a method called order. When called, this order method should ask the Customer to place an order by calling its placeOrder method. The Customer’s placeOrder method should in turn ask the Employee object for a new Food object by calling Employee’s takeOrder method.
- Food objects should store a food name string (e.g., “burritos”), passed down from Lunch.order, to Customer.placeOrder, to Employee.takeOrder, and finally to Food’s constructor. The top-level Lunch class should also export a method called result, which asks the customer to print the name of the food it received from the Employee via the order (this can be used to test your simulation).
Experiment with your classes interactively by importing the Lunch class, calling its order method to run an interaction, and then calling its result method to verify that the Customer got what he or she ordered. If you prefer, you can also simply code test cases as self-test code in the file where your classes are defined, using the module __name__ trick of Chapter 24. In this simulation, the Customer is the active agent; how would your classes change if Employee were the object that initiated customer/employee interaction instead?
Figure 31-1. A zoo hierarchy composed of classes linked into a tree to be searched by attribute inheritance. Animal has a common “reply” method, but each class may have its own custom “speak” method called by “reply”.
- Zoo animal hierarchy. Consider the class tree shown in Figure 31-1.
Code a set of six class statements to model this taxonomy with Python inheritance. Then, add a speak method to each of your classes that prints a unique message, and a reply method in your top-level Animal superclass that simply calls self.speak to invoke the category-specific message printer in a subclass below (this will kick off an independent inheritance search from self). Finally, remove the speak method from your Hacker class so that it picks up the default above it. When you’re finished, your classes should work this way:% python
>>> from zoo import Cat, Hacker
>>> spot = Cat()
>>> spot.reply() # Animal.reply; calls Cat.speak
meow
>>> data = Hacker() # Animal.reply; calls Primate.speak
>>> data.reply()
Hello world! - The Dead Parrot Sketch. Consider the object embedding structure captured in Figure 31-2.
Code a set of Python classes to implement this structure with composition. Code your Scene object to define an action method, and embed instances of the Customer, Clerk, and Parrot classes (each of which should define a line method that prints a unique message). The embedded objects may either inherit from a common superclass that defines line and simply provide message text, or define line themselves. In the end, your classes should operate like this:% python
>>> import parrot
>>> parrot.Scene().action() # Activate nested objects
customer: "that's one ex-bird!"
clerk: "no it isn't..."
parrot: None
Figure 31-2. A scene composite with a controller class (Scene) that embeds and directs instances of three other classes (Customer, Clerk, Parrot). The embedded instance’s classes may also participate in an inheritance hierarchy; composition and inheritance are often equally useful ways to structure classes for code reuse.
Why You Will Care: OOP by the Masters
When I teach Python classes, I invariably find that about halfway through the class, people who have used OOP in the past are following along intensely, while people who have not are beginning to glaze over (or nod off completely). The point behind the technology just isn’t apparent.
In a book like this, I have the luxury of including material like the new Big Picture overview in Chapter 25, and the gradual tutorial of Chapter 27—in fact, you should probably review that section if you’re starting to feel like OOP is just some computer science mumbo-jumbo.
In real classes, however, to help get the newcomers on board (and keep them awake), I have been known to stop and ask the experts in the audience why they use OOP. The answers they’ve given might help shed some light on the purpose of OOP, if you’re new to the subject.
Here, then, with only a few embellishments, are the most common reasons to use OOP, as cited by my students over the years:
Code reuse
This one’s easy (and is the main reason for using OOP). By supporting inheritance, classes allow you to program by customization instead of starting each project from scratch.
Encapsulation
Wrapping up implementation details behind object interfaces insulates users of a class from code changes.
Structure
Classes provide new local scopes, which minimizes name clashes. They also provide a natural place to write and look for implementation code, and to manage object state.
Maintenance
Classes naturally promote code factoring, which allows us to minimize redundancy. Thanks both to the structure and code reuse support of classes, usually only one copy of the code needs to be changed.
Consistency
Classes and inheritance allow you to implement common interfaces, and hence create a common look and feel in your code; this eases debugging, comprehension, and maintenance.
Polymorphism
This is more a property of OOP than a reason for using it, but by supporting code generality, polymorphism makes code more flexible and widely applicable, and hence more reusable.
Other
And, of course, the number one reason students gave for using OOP: it looks good on a résumé! (OK, I threw this one in as a joke, but it is important to be familiar with OOP if you plan to work in the software field today.)
Finally, keep in mind what I said at the beginning of this part of the book: you won’t fully appreciate OOP until you’ve used it for awhile. Pick a project, study larger examples, work through the exercises—do whatever it takes to get your feet wet with OO code; it’s worth the effort.