Learning Python - Mark Lutz [595]
Part VI, Classes and OOP
See Test Your Knowledge: Part VI Exercises in Chapter 31 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, as 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 " 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 “Operator Overloading” in Chapter 29. 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 this part of the book: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 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 find that you can 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 overloading methods that the text 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 in Python 2.2 and later, see Extending Types by Subclassing in Chapter 31: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 methods: sort/reverse/etc return getattr(self.wrapped, name) def __repr__(self): return repr(self.wrapped) if __name__ == '__main__': x = MyList('spam') print(x)