B.6 Part VI, Classes and OOP

See Section 23.5 for the exercises.

  1. Inheritance. Here's the solution code for this exercise (file adder.py), along with some interactive tests. The __add__ overload has to appear only once, in the superclass, since it invokes type-specific add methods in subclasses.

    class Adder:     def add(self, x, y):         print 'not implemented!'     def __init__(self, start=[  ]):         self.data = start     def __add__(self, other):                # Or in subclasses?         return self.add(self.data, other)    # Or return type? class ListAdder(Adder):     def add(self, x, y):         return x + y class DictAdder(Adder):     def add(self, x, y):         new = {  }         for k in x.keys(  ): new[k] = x[k]         for k in y.keys(  ): new[k] = y[k]         return new % python >>> from adder import * >>> x = Adder(  ) >>> x.add(1, 2) not implemented! >>> x = ListAdder(  ) >>> x.add([1], [2]) [1, 2] >>> x = DictAdder(  ) >>> x.add({1:1}, {2:2}) {1: 1, 2: 2} >>> x = Adder([1]) >>> x + [2] not implemented! >>> >>> x = ListAdder([1]) >>> x + [2] [1, 2] >>> [2] + x Traceback (innermost last):   File "<stdin>", line 1, in ? TypeError: __add__ nor __radd__ defined for these operands

    Notice in the last test that you get an error for expressions where a class instance appears on the right of a +; if you want to fix this, use __radd__ methods as described in Section 21.4 in Chapter 21.

    If you are saving a value in the instance anyhow, you might as well rewrite the add method to take just one argument, in the spirit of other examples in Part VI:

    class Adder:     def __init__(self, start=[  ]):         self.data = start     def __add__(self, other):        # Pass a single argument.         return self.add(other)           # The left side is in self.     def add(self, y):         print 'not implemented!' class ListAdder(Adder):     def add(self, y):         return self.data + y class DictAdder(Adder):     def add(self, y):         pass  # Change me to use self.data instead of x. x = ListAdder([1,2,3]) y = x + [4,5,6] print y               # Prints [1, 2, 3, 4, 5, 6]

    Because values are attached to objects rather than passed around, this version is arguably more object-oriented. And once you've gotten to this point, you'll probably see that you could get rid of add altogether, and simply define type-specific __add__ methods in the two subclasses.

  2. Operator overloading. The solution code (file mylist.py) uses a few operator overload methods we didn't say much about, but they should be straightforward to understand. Copying the initial value in the constructor is important, because it may be mutable; you don't want to change or have a reference to an object that's possibly shared somewhere outside the class. The __getattr__ method routes calls to the wrapped list. For hints on an easier way to code this as of Python 2.2, see Section 23.1.2 in Chapter 23.

    class MyList:     def __init__(self, start):         #self.wrapped = start[:]           # Copy start: no side effects         self.wrapped = [  ]                  # Make sure it's a list here.         for x in start: self.wrapped.append(x)     def __add__(self, other):         return MyList(self.wrapped + other)     def __mul__(self, time):         return MyList(self.wrapped * time)     def __getitem__(self, offset):         return self.wrapped[offset]     def __len__(self):         return len(self.wrapped)     def __getslice__(self, low, high):         return MyList(self.wrapped[low:high])     def append(self, node):         self.wrapped.append(node)     def __getattr__(self, name):       # Other members: sort/reverse/etc         return getattr(self.wrapped, name)     def __repr__(self):         return `self.wrapped` if __name__ == '__main__':     x = MyList('spam')     print x     print x[2]     print x[1:]     print x + ['eggs']     print x * 3     x.append('a')     x.sort(  )     for c in x: print c, % python mylist.py ['s', 'p', 'a', 'm'] a ['p', 'a', 'm'] ['s', 'p', 'a', 'm', 'eggs'] ['s', 'p', 'a', 'm', 's', 'p', 'a', 'm', 's', 'p', 'a', 'm'] a a m p s

    Note that it's important to copy the start value by appending instead of slicing here, because the result may other wise not be a true list, and so would not respond to expected list methods such as append (e.g., slicing a string returns another string, not a list). You would be able to copy a MyList start value by slicing, because its class overloads the slicing operation and provides the expected list interface. You need to avoid sliced-based copying for things such as strings, however.

  3. Subclassing. Our solution (mysub.py) appears below. Your solution should be similar.

    from mylist import MyList class MyListSub(MyList):     calls = 0                                 # Shared by instances     def __init__(self, start):         self.adds = 0                         # Varies in each instance         MyList.__init__(self, start)     def __add__(self, other):         MyListSub.calls = MyListSub.calls + 1   # Class-wide counter         self.adds = self.adds + 1               # Per instance counts         return MyList.__add__(self, other)     def stats(self):         return self.calls, self.adds                  # All adds, my adds if __name__ == '__main__':     x = MyListSub('spam')     y = MyListSub('foo')     print x[2]     print x[1:]     print x + ['eggs']     print x + ['toast']     print y + ['bar']     print x.stats(  ) % python mysub.py a ['p', 'a', 'm'] ['s', 'p', 'a', 'm', 'eggs'] ['s', 'p', 'a', 'm', 'toast'] ['f', 'o', 'o', 'bar'] (3, 2)
  4. Metaclass methods. We worked through this exercise as follows. Notice that operators try to fetch attributes through __getattr__ too; you need to return a value to make them work.

    >>> class Meta: ...     def __getattr__(self, name):         ...         print 'get', name ...     def __setattr__(self, name, value): ...         print 'set', name, value ... >>> x = Meta(  ) >>> x.append get append >>> x.spam = "pork" set spam pork >>> >>> x + 2 get __coerce__ Traceback (innermost last):   File "<stdin>", line 1, in ? TypeError: call of non-function >>> >>> x[1] get __getitem__ Traceback (innermost last):   File "<stdin>", line 1, in ? TypeError: call of non-function >>> x[1:5] get __len__ Traceback (innermost last):   File "<stdin>", line 1, in ? TypeError: call of non-function
  5. Set objects. Here's the sort of interaction you should get. Comments explain which methods are called.

    % python >>> from setwrapper import Set >>> x = Set([1,2,3,4])          # Runs __init__ >>> y = Set([3,4,5]) >>> x & y                       # __and__, intersect, then __repr__ Set:[3, 4] >>> x | y                       # __or__, union, then __repr__ Set:[1, 2, 3, 4, 5] >>> z = Set("hello")            # __init__ removes duplicates. >>> z[0], z[-1]                 # __getitem__  ('h', 'o') >>> for c in z: print c,        # __getitem__  ... h e l o >>> len(z), z                   # __len__, __repr__ (4, Set:['h', 'e', 'l', 'o']) >>> z & "mello", z | "mello" (Set:['e', 'l', 'o'], Set:['h', 'e', 'l', 'o', 'm'])

    Our solution to the multiple-operand extension subclass looks like the class below (file multiset.py). It only needs to replace two methods in the original set. The class's documentation string explains how it works.

    from setwrapper import Set class MultiSet(Set):     """     inherits all Set names, but extends intersect     and union to support multiple operands; note     that "self" is still the first argument (stored     in the *args argument now); also note that the     inherited & and | operators call the new methods     here with 2 arguments, but processing more than      2 requires a method call, not an expression:     """     def intersect(self, *others):         res = [  ]         for x in self:                     # Scan first sequence             for other in others:           # for all other args.                 if x not in other: break   # Item in each one?             else:                          # No: break out of loop                 res.append(x)              # Yes: add item to end         return Set(res)     def union(*args):                      # self is args[0].         res = [  ]         for seq in args:                   # For all args             for x in seq:                  # For all nodes                 if not x in res:                     res.append(x)          # Add new items to result.         return Set(res)

    Your interaction with the extension will be something along the following lines. Note that you can intersect by using & or calling intersect, but must call intersect for three or more operands; & is a binary (two-sided) operator. Also note that we could have called MutiSet simply Set to make this change more transparent if we used setwrapper.Set to refer to the original within multiset:

    >>> from multiset import * >>> x = MultiSet([1,2,3,4]) >>> y = MultiSet([3,4,5]) >>> z = MultiSet([0,1,2]) >>> x & y, x | y                               # Two operands (Set:[3, 4], Set:[1, 2, 3, 4, 5]) >>> x.intersect(y, z)                          # Three operands Set:[  ] >>> x.union(y, z) Set:[1, 2, 3, 4, 5, 0] >>> x.intersect([1,2,3], [2,3,4], [1,2,3])     # Four operands  Set:[2, 3] >>> x.union(range(10))                         # non-MultiSets work too. Set:[1, 2, 3, 4, 0, 5, 6, 7, 8, 9]
  6. Class tree links. Below is the way we changed the Lister class, and a rerun of the test to show its format. To display inherited class attributes too, you'd need to do something like what the attrnames method currently does, but recursively, at each class reached by climbing __bases__ links. Because dir includes inherited attributes in Python 2.2, you might also simply loop through its result: say for x in dir(self) and use getattr(self,x). This won't directly help, if you wish to represent the class tree's structure in your display like the classtree.py example in Chapter 21.

    class Lister:     def __repr__(self):         return ("<Instance of %s(%s), address %s:\n%s>" %                           (self.__class__.__name__,   # My class's name                            self.supers(  ),              # My class's supers                            id(self),                     # My address                            self.attrnames(  )) )         # name=value list     def attrnames(self):         ...unchanged ...     def supers(self):         result = ""         first = 1         for super in self.__class__.__bases__:   # One level up from class             if not first:                 result = result + ", "             first = 0             result = result + super.__name__      # name, not repr(super)          return result C:\python\examples> python testmixin.py  <Instance of Sub(Super, Lister), address 7841200:         name data3=42         name data2=eggs         name data1=spam >
  7. Composition. Our solution is below (file lunch.py), with comments from the description mixed in with the code. This is one case where it's probably easier to express a problem in Python than it is in English.

    class Lunch:     def __init__(self):            # Make/embed Customer and Employee.         self.cust = Customer(  )         self.empl = Employee(  )     def order(self, foodName):      # Start a Customer order simulation.         self.cust.placeOrder(foodName, self.empl)     def result(self):               # Ask the Customer about its Food.         self.cust.printFood(  ) class Customer:     def __init__(self):                         # Initialize my food to None.         self.food = None     def placeOrder(self, foodName, employee):  # Place order with Employee.         self.food = employee.takeOrder(foodName)     def printFood(self):                       # Print the name of my food.         print self.food.name class Employee:     def takeOrder(self, foodName):    # Return a Food, with requested name.         return Food(foodName) class Food:     def __init__(self, name):          # Store food name.         self.name = name if __name__ == '__main__':     x = Lunch(  )                       # Self-test code     x.order('burritos')                 # If run, not imported     x.result(  )     x.order('pizza')     x.result(  ) % python lunch.py burritos pizza
  8. Zoo Animal Hierarchy. Here is the way we coded the taxonomy on Python (file zoo.py); it's artificial, but the general coding pattern applies to many real structures from GUIs to employee databases. Notice that the self.speak reference in Animal triggers an independent inheritance search, which finds speak in a subclass. Test this interactively per the exercise description. Try extending this hierarchy with new classes, and making instances of various classes in the tree.

    class Animal:     def reply(self):   self.speak(  )        # Back to subclass     def speak(self):   print 'spam'          # Custom message class Mammal(Animal):     def speak(self):   print 'huh?' class Cat(Mammal):     def speak(self):   print 'meow' class Dog(Mammal):     def speak(self):   print 'bark' class Primate(Mammal):     def speak(self):   print 'Hello world!' class Hacker(Primate): pass                # Inherit from Primate.
  9. The Dead Parrot Sketch. Here's how we implemented this one (file parrot.py). Notice how the line method in the Actor superclass works: by accessing self attributes twice, it sends Python back to the instance twice, and hence invokes two inheritance searches self.name and self.says( ) find information in the specific subclasses.

    class Actor:     def line(self): print self.name + ':', `self.says(  )` class Customer(Actor):     name = 'customer'     def says(self): return "that's one ex-bird!" class Clerk(Actor):     name = 'clerk'     def says(self): return "no it isn't..." class Parrot(Actor):     name = 'parrot'     def says(self): return None class Scene:     def __init__(self):         self.clerk    = Clerk(  )       # Embed some instances.         self.customer = Customer(  )    # Scene is a composite.         self.subject  = Parrot(  )     def action(self):         self.customer.line(  )          # Delegate to embedded.         self.clerk.line(  )         self.subject.line(  )


Learning Python
Learning Python: Powerful Object-Oriented Programming
ISBN: 0596158068
EAN: 2147483647
Year: 2003
Pages: 253
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net