I l@ve RuBoard Previous Section Next Section

6.12 Exercises

This laboratory session asks 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 the 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. If you're pressed for time, we suspect that the last exercise dealing with composition will probably be the most fun of the bunch (of course, we already know the answers).

  1. The basics. 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. Finally, extend your classes to save an object in a constructor (a list or a dictionary) and overload the + operator to replace the add method. 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?

  2. Operator overloading. Write a class called Mylist that "wraps" a Python list: it should overload most list operators and operations—+, indexing, iteration, slicing, and list methods such as append and sort. See the Python reference manual for a list of all possible methods to overload. 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; how about indexing?

  3. Subclassing. Now, 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. For instance, 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 this chapter (from Section 6.8.5). 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 4). 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 a footnote in the section on multiple inheritance, 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, so that it prints the names of the immediate superclasses of the instance's class too. When you're done, the first line of the string representation should look like this:

    <Instance of Sub(Super, Lister), address 7841200:.

    How would you go about listing class attributes too?

  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:

    • 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.

    • 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 (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.

  8. 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. 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?

I l@ve RuBoard Previous Section Next Section