Classes Can Intercept Python Operators

Let’s move on to the third and final major difference between classes and modules: 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 and other built-in operations 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, and it allows class-based objects to be processed by code written to expect a built-in type’s interface. Here is a quick rundown of the main ideas behind overloading operators:

Operator overloading is an optional feature; it’s used primarily by people developing tools for other Python programmers, not by application developers. And, candidly, you probably shouldn’t use it just because it seems clever or “cool.” Unless a class needs to mimic built-in type interfaces, it should usually stick to simpler named methods. Why would an employee database application support expressions like * and +, for example? Named methods like giveRaise and promote would usually make more sense.

Because of this, we won’t go into details on every operator overloading method available in Python in this book. Still, there is one operator overloading method you are likely to see in almost every realistic Python class: the __init__ method, which is known as the constructor method and is used to initialize objects’ state. You should pay special attention to this method, because __init__, along with the self argument, turns out to be a key requirement to reading and understanding most OOP code in Python.

A Third Example

On to another example. This time, we’ll define a subclass of the prior section’s SecondClass that implements three specially named attributes that Python will call automatically:

  • __init__ is run when a new instance object is created: self is the new ThirdClass object.[53]

  • __add__ is run when a ThirdClass instance appears in a + expression.

  • __str__ is run when an object is printed (technically, when it’s converted to its print string by the str built-in function or its Python internals equivalent).

Our new subclass also defines a normally named method called mul, which changes the instance object in place. Here’s the new subclass:

>>> class ThirdClass(SecondClass):                     # Inherit from SecondClass
        def __init__(self, value):                     # On "ThirdClass(value)"
            self.data = value
        def __add__(self, other):                      # On "self + other"
            return ThirdClass(self.data + other)
        def __str__(self):                             # On "print(self)", "str()"
            return '[ThirdClass: %s]' % self.data
        def mul(self, other):                          # In-place change: named
            self.data *= other

>>> a = ThirdClass('abc')           # __init__ called
>>> a.display()                     # Inherited method called
Current value = "abc"
>>> print(a)                        # __str__: returns display string
[ThirdClass: abc]

>>> b = a + 'xyz'                   # __add__: makes a new instance
>>> b.display()                     # b has all ThirdClass methods
Current value = "abcxyz"
>>> print(b)                        # __str__: returns display string
[ThirdClass: abcxyz]

>>> a.mul(3)                        # mul: changes instance in place
>>> print(a)
[ThirdClass: abcabcabc]

ThirdClass “is a” SecondClass, so its instances inherit the customized display method from SecondClass of the preceding section. This time, though, ThirdClass creation calls pass an argument (e.g., “abc”). This argument is passed to the value argument in the __init__ constructor and assigned to self.data there. The net effect is that ThirdClass arranges to set the data attribute automatically at construction time, instead of requiring setdata calls after the fact.

Further, ThirdClass objects can now show up in + expressions and print calls. For +, Python passes the instance object on the left to the self argument in __add__ and the value on the right to other, as illustrated in Figure 27-3; whatever __add__ returns becomes the result of the + expression (more on its result in a moment).

For print, Python passes the object being printed to self in __str__; whatever string this method returns is taken to be the print string for the object. With __str__ (or its more broadly relevant twin __repr__, which we’ll meet and use in the next chapter), we can use a normal print to display objects of this class, instead of calling the special display method.

In operator overloading, expression operators and other built-in operations performed on class instances are mapped back to specially named methods in the class. These special methods are optional and may be inherited as usual. Here, a + expression triggers the __add__ method.

Figure 27-3. In operator overloading, expression operators and other built-in operations performed on class instances are mapped back to specially named methods in the class. These special methods are optional and may be inherited as usual. Here, a + expression triggers the __add__ method.

Specially named methods such as __init__, __add__, and __str__ are inherited by subclasses and instances, just like any other names assigned in a class. If they’re not coded in a class, Python looks for such names in all its 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. Python usually calls them automatically, but they may occasionally be called by your code as well. For example, the __init__ method is often called manually to trigger initialization steps in a superclass, as we’ll see in the next chapter.

Returning results, or not

Some operator overloading methods like __str__ require results, but others are more flexible. For example, notice how the __add__ method makes and returns a new instance object of its class, by calling ThirdClass with the result value—which in turn triggers __init__ to initialize the result. This is a common convention, and explains why b in the listing has a display method; it’s a ThirdClass object too, because that’s what + returns for this class’s objects. This essentially propagates the type.

By contrast, mul changes the current instance object in place, by reassigning the self attribute. We could overload the * expression to do the latter, but this would be too different from the behavior of * for built-in types such as numbers and strings, for which it always makes new objects. Common practice dictates that overloaded operators should work the same way that built-in operator implementations do. Because operator overloading is really just an expression-to-method dispatch mechanism, though, you can interpret operators any way you like in your own class objects.

Why Use 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 built-in types. As mentioned earlier, if you omit an operator overloading method and do not inherit it from a superclass, the corresponding operation will not be supported for your instances; if it’s attempted, an exception will be raised (or, in some cases like printing, a standard default will be used).

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

On the other hand, you might decide to 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. Implementing the same operator set in your class will ensure that your objects support the same expected object interface and so are compatible with the function. Although we won’t cover every operator overloading method in this book, we’ll survey additional common operator overloading techniques in action in Chapter 30.

One overloading method we will use often here is the __init__ constructor method, used to initialize newly created instance objects, and present in almost every realistic class. Because it allows classes to fill out the attributes in their new 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.

Of course, there’s nothing wrong with experimenting with interesting language tools, but they don’t always translate to production code. With time and experience, you’ll find these programming patterns and guidelines to be natural and nearly automatic.



[53] Not to be confused with the __init__.py files in module packages! The method here is a class constructor function used to initialize the newly created instance, not a module package. See Chapter 24 for more details.