Our next group of overloading methods extends the functionality of binary operator methods such as __add__ and __sub__ (called for + and -), which we’ve already seen. As mentioned earlier, part of the reason there are so many operator overloading methods is because they come in multiple flavors—for every binary expression, we can implement a left, right, and in-place variant. Though defaults are also applied if you don’t code all three, your objects’ roles dictate how many variants you’ll need to code.
For instance, the __add__ methods coded so far technically do not support the use of instance objects on the right side of the + operator:
>>>class Adder:def __init__(self, value=0):self.data = valuedef __add__(self, other):return self.data + other>>>x = Adder(5)>>>x + 27 >>>2 + xTypeError: unsupported operand type(s) for +: 'int' and 'Adder'
To implement more general expressions, and hence support commutative-style operators, code the __radd__ method as well. Python calls __radd__ only when the object on the right side of the + is your class instance, but the object on the left is not an instance of your class. The __add__ method for the object on the left is called instead in all other cases (all of this section’s five Commuter classes are coded in file commuter.py in the book’s examples, along with a self-test):
class Commuter1:
def __init__(self, val):
self.val = val
def __add__(self, other):
print('add', self.val, other)
return self.val + other
def __radd__(self, other):
print('radd', self.val, other)
return other + self.val
>>> from commuter import Commuter1
>>> x = Commuter1(88)
>>> y = Commuter1(99)
>>> x + 1 # __add__: instance + noninstance
add 88 1
89
>>> 1 + y # __radd__: noninstance + instance
radd 99 1
100
>>> x + y # __add__: instance + instance, triggers __radd__
add 88 <commuter.Commuter1 object at 0x00000000029B39E8>
radd 99 88
187
Notice how the order is reversed in __radd__: self is really on the right of the +, and other is on the left. Also note that x and y are instances of the same class here; when instances of different classes appear mixed in an expression, Python prefers the class of the one on the left. When we add the two instances together, Python runs __add__, which in turn triggers __radd__ by simplifying the left operand.
For truly commutative operations that do not require special-casing by position, it is also sometimes sufficient to reuse __add__ for __radd__: either by calling __add__ directly; by swapping order and re-adding to trigger __add__ indirectly; or by simply assigning __radd__ to be an alias for __add__ at the top level of the class statement (i.e., in the class’s scope). The following alternatives implement all three of these schemes, and return the same results as the original—though the last saves an extra call or dispatch and hence may be quicker (in all, __radd__ is run when self is on the right side of a +):
class Commuter2:
def __init__(self, val):
self.val = val
def __add__(self, other):
print('add', self.val, other)
return self.val + other
def __radd__(self, other):
return self.__add__(other) # Call __add__ explicitly
class Commuter3:
def __init__(self, val):
self.val = val
def __add__(self, other):
print('add', self.val, other)
return self.val + other
def __radd__(self, other):
return self + other # Swap order and re-add
class Commuter4:
def __init__(self, val):
self.val = val
def __add__(self, other):
print('add', self.val, other)
return self.val + other
__radd__ = __add__ # Alias: cut out the middleman
In all these, right-side instance appearances trigger the single, shared __add__ method, passing the right operand to self, to be treated the same as a left-side appearance. Run these on your own for more insight; their returned values are the same as the original.
In more realistic classes where the class type may need to be propagated in results, things can become trickier: type testing may be required to tell whether it’s safe to convert and thus avoid nesting. For instance, without the isinstance test in the following, we could wind up with a Commuter5 whose val is another Commuter5 when two instances are added and __add__ triggers __radd__:
class Commuter5: # Propagate class type in results def __init__(self, val): self.val = val def __add__(self, other): if isinstance(other, Commuter5): # Type test to avoid object nesting other = other.val return Commuter5(self.val + other) # Else + result is another Commuter def __radd__(self, other): return Commuter5(other + self.val) def __str__(self): return '<Commuter5: %s>' % self.val >>>from commuter import Commuter5>>>x = Commuter5(88)>>>y = Commuter5(99)>>>print(x + 10)# Result is another Commuter instance <Commuter5: 98> >>>print(10 + y)<Commuter5: 109> >>>z = x + y# Not nested: doesn't recur to __radd__ >>>print(z)<Commuter5: 187> >>>print(z + 10)<Commuter5: 197> >>>print(z + z)<Commuter5: 374> >>>print(z + z + 1)<Commuter5: 375>
The need for the isinstance type test here is very subtle—uncomment, run, and trace to see why it’s required. If you do, you’ll see that the last part of the preceding test winds up differing and nesting objects—which still do the math correctly, but kick off pointless recursive calls to simplify their values, and extra constructor calls build results:
>>>z = x + y# With isinstance test commented-out >>>print(z)<Commuter5: <Commuter5: 187>> >>>print(z + 10)<Commuter5: <Commuter5: 197>> >>>print(z + z)<Commuter5: <Commuter5: <Commuter5: <Commuter5: 374>>>> >>>print(z + z + 1)<Commuter5: <Commuter5: <Commuter5: <Commuter5: 375>>>>
To test, the rest of commuter.py looks and runs like this—classes can appear in tuples naturally:
#!python from __future__ import print_function # 2.X/3.X compatibility...classes defined here...if __name__ == '__main__': for klass in (Commuter1, Commuter2, Commuter3, Commuter4, Commuter5): print('-' * 60) x = klass(88) y = klass(99) print(x + 1) print(1 + y) print(x + y) c:\code>commuter.py------------------------------------------------------------ add 88 1 89 radd 99 1 100 add 88 <__main__.Commuter1 object at 0x000000000297F2B0> radd 99 88 187 ------------------------------------------------------------...etc...
There are too many coding variations to explore here, so experiment with these classes on your own for more insight; aliasing __radd__ to __add__ in Commuter5, for example, saves a line, but doesn’t prevent object nesting without isinstance. See also Python’s manuals for a discussion of other options in this domain; for example, classes may also return the special NotImplemented object for unsupported operands to influence method selection (this is treated as though the method were not defined).
To also implement += in-place augmented addition, code either an __iadd__ or an __add__. The latter is used if the former is absent. In fact, the prior section’s Commuter classes already support += for this reason—Python runs __add__ and assigns the result manually. The __iadd__ method, though, allows for more efficient in-place changes to be coded where applicable:
>>>class Number:def __init__(self, val):self.val = valdef __iadd__(self, other):# __iadd__ explicit: x += yself.val += other# Usually returns selfreturn self>>>x = Number(5)>>>x += 1>>>x += 1>>>x.val7
For mutable objects, this method can often specialize for quicker in-place changes:
>>>y = Number([1])# In-place change faster than + >>>y += [2]>>>y += [3]>>>y.val[1, 2, 3]
The normal __add__ method is run as a fallback, but may not be able optimize in-place cases:
>>>class Number:def __init__(self, val):self.val = valdef __add__(self, other):# __add__ fallback: x = (x + y)return Number(self.val + other)# Propagates class type >>>x = Number(5)>>>x += 1>>>x += 1# And += does concatenation here >>>x.val7
Though we’ve focused on + here, keep in mind that every binary operator has similar right-side and in-place overloading methods that work the same (e.g., __mul__, __rmul__, and __imul__). Still, right-side methods are an advanced topic and tend to be fairly uncommon in practice; you only code them when you need operators to be commutative, and then only if you need to support such operators at all. For instance, a Vector class may use these tools, but an Employee or Button class probably would not.