I l@ve RuBoard |
We introduced operator overloading at the start of this chapter; let's fill in a few blanks here and look at a handful of commonly used overloading methods. Here's a review of the key ideas behind overloading:
Operator overloading lets classes intercept normal Python operations.
Classes can overload all Python expression operators.
Classes can also overload object operations: printing, calls, qualification, etc.
Overloading makes class instances act more like built-in types.
Overloading is implemented by providing specially named class methods.
Here's a simple example of overloading at work. When we provide specially named methods in a class, Python automatically calls them when instances of the class appear in the associated operation. For instance, the Number class below provides a method to intercept instance construction ( __ init __ ), as well as one for catching subtraction expressions ( __ sub __ ). Special methods are the hook that lets you tie into built-in operations:
class Number: def __init__(self, start): # on Number(start) self.data = start def __sub__(self, other): # on instance - other return Number(self.data - other) # result is a new instance >>> from number import Number # fetch class from module >>> X = Number(5) # calls Number.__init__(X, 5) >>> Y = X - 2 # calls Number.__sub__(X, 2) >>> Y.data 3
Just about everything you can do to built-in objects such as integers and lists has a corresponding specially named method for overloading in classes. Table 6.1 lists a handful of the most common; there are many more than we have time to cover in this book. See other Python books or the Python Library Reference Manual for an exhaustive list of special method names available. All overload methods have names that start and end with two underscores, to keep them distinct from other names you define in your classes.
Method |
Overloads |
Called for |
---|---|---|
__init__ |
Constructor |
Object creation: Class() |
__del__ |
Destructor |
Object reclamation |
Operator '+' |
X + Y |
|
__or__ |
Operator '|' (bitwise or) |
X | Y |
__repr__ |
Printing, conversions |
print X, `X` |
__call__ |
Function calls |
X() |
__getattr__ |
Qualification |
X.undefined |
__getitem__ |
Indexing |
X[key], for loops, in tests |
__setitem__ |
Index assignment |
X[key] = value |
__getslice__ |
Slicing |
X[low:high] |
__len__ |
Length |
len(X), truth tests |
__cmp__ |
Comparison |
X == Y, X < Y |
__radd__ |
Right-side operator '+' |
Noninstance + X |
Let's illustrate a few of the methods in Table 6.1 by example.
The __ getitem __ method intercepts instance indexing operations: When an instance X appears in an indexing expression like X[i], Python calls a __ getitem __ method inherited by the instance (if any), passing X to the first argument and the index in brackets to the second argument. For instance, the following class returns the square of index values:
>>> class indexer: ... def __getitem__(self, index): ... return index ** 2 ... >>> X = indexer() >>> for i in range(5): ... print X[i], # X[i] calls __getitem__(X, i) ... 0 1 4 9 16
Now, here's a special trick that isn't always obvious to beginners, but turns out to be incredibly useful: when we introduced the for statement back in Chapter 3, we mentioned that it works by repeatedly indexing a sequence from zero to higher indexes, until an out-of-bounds exception is detected. Because of that, __ getitem __ also turns out to be the way to overload iteration and membership tests in Python. It's a case of "buy one, get two free": any built-in or user-defined object that responds to indexing also responds to iteration and membership automatically:
>>> class stepper: ... def __getitem__(self, i): ... return self.data[i] ... >>> X = stepper() # X is a stepper object >>> X.data = "Spam" >>> >>> for item in X: # for loops call __getitem__ ... print item, # for indexes items 0..N ... S p a m >>> >>> 'p' in X # 'in' operator calls __getitem__ too 1
The __getattr__ method intercepts attribute qualifications. More specifically, it's called with the attribute name as a string, whenever you try to qualify an instance on an undefined (nonexistent) attribute name. It's not called if Python can find the attribute using its inheritance tree-search procedure. Because of this behavior, _ _getattr__ is useful as a hook for responding to attribute requests in a generic fashion. For example:
>>> class empty: ... def __getattr__(self, attrname): ... if attrname == "age": ... return 36 ... else: ... raise AttributeError, attrname ... >>> X = empty() >>> X.age 36 >>> X.name Traceback (innermost last): File "<stdin>", line 1, in ? File "<stdin>", line 6, in __getattr__ AttributeError: name
Here, the empty class and its instance X have no real attributes of their own, so the access to X.age gets routed to the __ getattr __ method; self is assigned the instance (X), and attrname is assigned the undefined attribute name string ("age"). Our class makes age look like a real attribute by returning a real value as the result of the X.age qualification expression (36).
For other attributes the class doesn't know how to handle, it raises the built-in AttributeError exception, to tell Python that this is a bona fide undefined name; asking for X.name triggers the error. We'll see __ getattr __ again when we show delegation at work, and we will say more about exceptions in Chapter 7.
Here's an example that exercises the _ _ init __ constructor and the __ add __ + overload methods we've already seen, but also defines a __ repr __ that returns a string representation of instances. Backquotes are used to convert the managed self.data object to a string. If defined, __ repr __ is called automatically when class objects are printed or converted to strings.
>>> class adder: ... def __init__(self, value=0): ... self.data = value # initialize data ... def __add__(self, other): ... self.data = self.data + other # add other in-place ... def __repr__(self): ... return `self.data` # convert to string ... >>> X = adder(1) # __init__ >>> X + 2; X + 2 # __add__ >>> X # __repr__ 5
That's as many overloading examples as we have space for here. Most work similarly to ones we've already seen, and all are just hooks for intercepting built-in type operations we've already studied; but some overload methods have unique argument lists or return values. We'll see a few others in action later in the text, but for a complete coverage, we'll defer to other documentation sources.
I l@ve RuBoard |