I l@ve RuBoard Previous Section Next Section

6.6 Operator Overloading in Classes

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:

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

6.6.1 Common Operator Overloading Methods

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.

Table?.1. A Sampling of Operator Overloading Methods

Method

Overloads

Called for

__init__

Constructor

Object creation: Class()

__del__

Destructor

Object reclamation

_ _add__

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

6.6.2 Examples

Let's illustrate a few of the methods in Table 6.1 by example.

6.6.2.1 __getitem__ intercepts all index references

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
6.6.2.2 __getattr__ catches undefined attribute references

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.

6.6.2.3 _ _repr__ returns a string representation

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 Previous Section Next Section