20.3 Classes Can Intercept Python Operators

Let's take a look at the third major distinction of classes: operator overloading. In simple terms, operator overloading lets objects coded with classes intercept and respond to operations that work on built-in types: addition, slicing, printing, qualification, and so on. It's mostly just an automatic dispatch mechanism: expressions route control to implementations in classes. Here, too, there is nothing similar in modules: modules can implement function calls, but not the behavior of expressions.

Although we could implement all class behavior as method functions, operator overloading lets objects be more tightly integrated with Python's object model. Moreover, because operator overloading makes our own objects act like built-ins, it tends to foster object interfaces that are more consistent and easier to learn. Here are the main ideas behind overloading operators:

  • Methods with names such as __X__ are special hooks. Python operator overloading is implemented by providing specially named methods to intercept operations.

  • Such methods are called automatically when Python evaluates operators. For instance, if an object inherits an __add__ method, it is called when the object appears in a + expression.

  • Classes may override most built-in type operations. There are dozens of special operator method names, for intercepting and implementing nearly every operation available for built-in types.

  • Operators allow classes to integrate with Python's object model. By overloading type operations, user-defined objects implemented with classes act just like built-ins, and so provide consistency.

20.3.1 A Third Example

On to another example. This time, we define a subclass of SecondClass, which implements three specially-named attributes that Python will call automatically: __init__ is called when a new instance object is being constructed (self is the new ThirdClass object), and __add__ and __mul__ are called when a ThirdClass instance appears in + and * expressions, respectively:

>>> class ThirdClass(SecondClass):                    # is-a SecondClass ...     def __init__(self, value):                # On "ThirdClass(value)" ...         self.data = value ...     def __add__(self, other):                 # On "self + other" ...         return ThirdClass(self.data + other) ...     def __mul__(self, other): ...         self.data = self.data * other          # On "self * other"  >>> a = ThirdClass("abc")       # New __init__ called >>> a.display(  )               # Inherited method Current value = "abc" >>> b = a + 'xyz'             # New __add__: makes a new instance >>> b.display(  ) Current value = "abcxyz" >>> a * 3                     # New __mul__: changes instance in-place >>> a.display(  ) Current value = "abcabcabc"

ThirdClass is a SecondClass, so its instances inherit display from SecondClass. But ThirdClass generation calls pass an argument now (e.g., "abc"); it's passed to the value argument in the __init__ constructor and assigned to self.data there. Further, ThirdClass objects can show up in + and * expressions; Python passes the instance object on the left to the self argument and the value on the right to other, as illustrated in Figure 20-3.

Figure 20-3. Operators map to special methods
figs/lpy2_2003.gif

Specially-named methods such as __init__ and __add__ are inherited by subclasses and instances, just like any other name assigned in a class. If the methods are not coded in a class, Python looks for such names in all superclasses as usual. Operator overloading method names are also not built-in or reserved words: they are just attributes that Python looks for when objects appear in various contexts. They are usually called by Python automatically, but may occasionally be called by your code as well.

Notice that the __add__ method makes and returns a new instance object of its class (by calling ThirdClass with the result value), but __mul__ changes the current instance object in place (by reassigning a self attribute). This is different than the behavior of built-in types such as numbers and strings, which always make a new object for the * operator. Because operator overloading is really just an expression-to-method dispatch mechanism, you can interpret operators any way you like in your own class objects.[1]

[1] But you probably shouldn't. Common practice dictates that overloaded operators should work the same way built-in operator implementations do. In this case, that means our __mul__ method should return a new object as its result, rather than changing the instance (self) in place; for in-place changes, a mul method call may be better style than a * overload here (e.g., a.mul(3) instead of a * 3).

20.3.2 Why Operator Overloading?

As a class designer, you can choose to use operator overloading or not. Your choice simply depends on how much you want your object to look and feel like a built-in type. If you omit an overloading operator method and do not inherit it from a superclass, the corresponding operation is not supported for your instances, and will simply throw an exception (or use a standard default) if attempted.

Frankly, many operator overloading methods tend to be used only when implementing objects that are mathematical in nature; a vector or matrix class may overload addition, for example, but an employee class likely would not. For simpler classes, you might not use overloading at all, and rely instead on explicit method calls to implement your object's behavior.

On the other hand, you might also use operator overloading if you need to pass a user-defined object to a function that was coded to expect the operators available on a built-in type like a list or a dictionary. By implementing the same operator set in your class, your objects will support the same expected object interface, and so will be compatible with the function.

Typically, one overloading method seems to show up in almost every realistic class: the __init__ constructor method. Because it allows classes to fill out the attributes in their newly-created instances immediately, the constructor is useful for almost every kind of class you might code. In fact, even though instance attributes are not declared in Python, you can usually find out which attributes an instance will have by inspecting its class's __init__ method code. We'll see additional inheritance and operator overloading techniques in action in Chapter 21 .



Learning Python
Learning Python: Powerful Object-Oriented Programming
ISBN: 0596158068
EAN: 2147483647
Year: 2003
Pages: 253
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net