See Section 23.5
for the exercises.
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. 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. 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) 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 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] 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
> 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 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. 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( )
|