Learning Python - Mark Lutz [596]
print(x[2])
print(x[1:])
print(x + ['eggs'])
print(x * 3)
x.append('a')
x.sort()
for c in x: print(c, end=' ')
% 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 otherwise the result may not be a true list and so will 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; however, you need to avoid slice-based copying for objects such as strings. Also, note that sets are a built-in type in Python today, so this is largely just a coding exercise (see Chapter 5 for more on sets).
Subclassing. My 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 += 1 # Class-wide counter
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)
Attribute methods. I worked through this exercise as follows. Notice that in Python 2.6, operators try to fetch attributes through __getattr__, too; you need to return a value to make them work. Caveat: as noted in Chapter 30, __getattr__ is not called for built-in operations in Python 3.0, so the following expression won’t work as shown; in 3.0, a class like this must redefine __X__ operator overloading methods explicitly. More on this in Chapters 30, 37, and 38.>>> 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 " TypeError: call of non-function >>> >>> x[1] get __getitem__ Traceback (innermost last): File " TypeError: call of non-function >>> x[1:5] get __len__ Traceback (innermost last): File " 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, end=' ') # __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']) My solution to the multiple-operand extension subclass looks like the following class (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):