DekGenius.com
[ Team LiB ] Previous Section Next Section

23.5 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 down the class source code off the Internet (see Preface) or type it up by hand (it's fairly small). These programs are starting to get more sophisticated, so be sure to check the solutions at the end of the book for pointers.

See Section B.6 for the solutions.

  1. 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 with 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__ 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 overload 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"?

  2. 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:

    1. Why is copying the initial value important here?

    2. Can you use an empty slice (e.g., start[:]) to copy the initial value if it's a Mylist instance?

    3. Is there a general way to route list method calls to the wrapped list?

    4. Can you add a Mylist and a regular list? How about a list and a Mylist instance?

    5. What type of object should operations like + and slicing return; how about indexing?

    6. If you are working with a more recent Python release (Version 2.2 or later), you may implement this sort of wrapper class either 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?

  3. 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 displays 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 both of these? (Hint: it depends on which object the count members are assigned to: class members are shared by instances, self members are per-instance data.)

  4. Metaclass methods. Write a class called Meta with methods that intercept every attribute qualification (both fetches and assignments) and prints a message with 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.

  5. Set objects. Experiment with the set class described in Section 23.1.1. Run commands to do the following sorts of operations:

    1. Create two sets of integers, and compute their intersection and union by using & and | operator expressions.

    2. Create a set from a string, and experiment with indexing your set; which methods in the class are called?

    3. Try iterating through the items in your string set using a for loop; which methods run this time?

    4. Try computing the intersection and union of your string set and a simple Python string; does it work?

    5. Now, extend your set by subclassing to handle arbitrarily many operands using a *args argument form (Hint: see the function versions of these algorithms in Chapter 13). 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?

    6. How would you go about emulating other list operations in the set class? (Hints: __add__ can catch concatenation, and __getattr__ can pass most list method calls off to the wrapped list.)

  6. Class tree links. In Section 21.5 in Chapter 21, and in Section 22.6 in Chapter 22, we mentioned that classes have a __bases__ attribute that returns a tuple of the class's superclass objects (the ones in parentheses in the class header). Use __bases__ to extend the Lister mixin class (see Chapter 22), so that it prints 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:

    How would you go about listing inherited class attributes too? (Hint: classes have a __dict__.) Try extending your Lister class to display all accessible superclasses and their attributes as well; see Chapter 21s classtree.py example for hints on climbing class trees, and the Lister footnote about using dir and getattr in Python 2.2 for hints on climbing trees.

  7. Composition. Simulate a fast-food ordering scenario by defining four classes:


    Lunch

    A container and controller class


    Customer

    The actor that buys food


    Employee

    The actor that a customer orders from


    Food

    What the customer buys

    To get you started, here are the classes and methods you'll be defining:

    class Lunch:
        def __init__(self)          # Make/embed Customer and Employee.
        def order(self, foodName)  # Start a Customer order simulation.
        def result(self)           # Ask the Customer what kind of 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 order simulation works as follows:

    1. The Lunch class's constructor should make and embed an instance of Customer and Employee, and 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 the Employee's takeOrder method.

    2. 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).

    Note that Lunch needs to either pass the Employee to the Customer, or pass itself to the Customer, in order to allow the Customer to call Employee methods.

    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 in Chapter 18. 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?

  8. Zoo Animal Hierarchy: Consider the class tree shown in Figure 23-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!
Figure 23-1. A zoo hierarchy
figs/lpy2_2301.gif
  1. The Dead Parrot Sketch: Consider the object embedding structure captured in Figure 23-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 Customer, Clerk, and Parrot classes—all three 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 23-2. A scene composite
figs/lpy2_2302.gif
    [ Team LiB ] Previous Section Next Section