We’ve seen that the usual arithmetic operators +, -, * and / can be used with Python’s numeric data types, and that some of these operators also have meaning when applied to other data types. For example, we can join two strings using +, add a list onto another list using + and so on.

These are examples of operator overloading, in which the same operator such as + has a meaning which depends on its operands (the quantities on which it operates).

Like many object oriented languages, Python allows you to overload operators so that they can modified to suit your own user-defined classes. To do this, we need to know the special names that Python uses to implement its various operators.

There are a large number of operators that can be overloaded. In addition to what we normally think of as operators (arithmetic, comparison and so on), other symbols such as parentheses and brackets are also operators and can be overloaded. In order not to ‘overload’ the reader at this stage, though, we’ll have a look at just the simple arithmetic operators.

Let’s say you want to define a class to represent a mathematical vector. The standard type of vector you have probably encountered in high school mathematics classes is defined as an object that has both magnitude and direction, and, in two or three dimensions at least, can be represented by an arrow in the corresponding space.

We’ll generalize this definition a bit by allowing our vectors to have any number of dimensions. This might sound esoteric, but in fact, we can represent a vector in dimensions by a list containing numbers. In Python code, a bare-bones vector class would therefore be given by the file Vector.py:

class Vector(object):
"""a mathematical vector"""
def __init__(self, comps):
self.comps = comps

Here, comps (short for ‘components’) is assumed to be a Python list.

Now suppose we would like to be able to add two such Vectors using the + operator. That is, we’d like to be able to write code like:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

vec3 = vec1 + vec2

We would like vec3 to be a Vector object with components [6.2, 8, 10, 12].

## Operator methods

To do this, we need to understand that Python implements all its operators by running a special method for each one. These methods all have names that begin and end with a double underscore. Thus the statement vec3 = vec1 + vec2 is implemented by running the statement vec3 = vec1.__add__(vec2). The method __add__ is the special method that implements the + operator, and Python has built-in definitions of __add__ for many of its built-in data types, as we mentioned above.

If we try to run the code above using the bare-bones Vector class, we’ll get an error that says: TypeError: unsupported operand type(s) for +: ‘Vector’ and ‘Vector’. We are told that the + operator isn’t defined when its operands are Vectors.

To fix this, we need to write our own version of __add__ that does take Vectors as operands. Here’s a modified version of the Vector class that does this:

class Vector(object):
"""a mathematical vector"""
def __init__(self, comps):
self.comps = comps

if isinstance(other, Vector):
if len(other.comps) == len(self.comps):
return Vector([self.comps[i] + other.comps[i]
for i in range(len(self.comps))])
else:
return None
else:
return None


The overloaded __add__ method is on lines 6 to 14. It takes two arguments: self and other. self, as usual, refers to the object (a Vector) that calls the method, and is the Vector that appears on the left side of the + operator. The other object is the Vector on the right side of the +.

We want the overloaded + to handle the case where both its operands are Vectors. Since it is defined within the Vector class, self is guaranteed to be a Vector, so we need check only that the other operand is also a Vector, which we do with the isinstance() method on line 7. As vector addition is defined only if the two vectors have the same dimension, we check that the lengths of the comps lists are equal, and if they are, we create and return a new Vector whose comps list is the sum of the comps lists in the two operands.

At this point, we reject any + operation that doesn’t satisfy the requirement that both operands are Vectors of the same dimension, by returning None in all these cases. In practice, we would probably want to make things a bit more user-friendly by printing error messages, but we don’t want to clutter up the code here.

We now test the Vector class with the code:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

vec3 = vec1 + vec2
print(vec3.comps)

We find that it does indeed give us what we want. The components are [6.2, 8, 10, 12].

Now suppose we want to expand the meaning of the + operator so that if its left operand is a Vector and its right operand is a number (int or float), then the Vector’s comps list is extended by appending the number onto the end of the list, effectively increasing the dimension of the Vector by 1. In this case, the original Vector is changed so we don’t want to create a new vector.

We can do this by modifying __add__ as shown:

class Vector(object):
"""a mathematical vector"""
def __init__(self, comps):
self.comps = comps

if isinstance(other, Vector):
if len(other.comps) == len(self.comps):
return Vector([self.comps[i] + other.comps[i]
for i in range(len(self.comps))])
else:
return None
elif isinstance(other, (int, float)):
self.comps += [other]
return self
else:
return None


The extra code is on lines 13 to 15. We check that other is a numeric type (int or float) and, if so, we append other onto the end of self.comps, and return self, which is the original Vector object, now modified with the addition of an extra component.

We can test this code with:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

vec1 + 9
print(vec1.comps)


We now get [1.2, 2, 3, 4, 9] printed out, showing that the component 9 has indeed been added to vec1.

## Right operands calling overloaded operators

Now suppose we want a way of adding an extra element to the beginning of the comps list, rather than the end. A natural way of doing this would be to allow an expression like 10.7 + vec2, with the new element as the left operand and the Vector as the right operand. We might try this:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

10.7 + vec2
print(vec2.comps)


We get the error: TypeError: unsupported operand type(s) for +: ‘float’ and ‘Vector’. The problem is that the __add__ function always treats its self argument as the left operand of +, which is this case is a float, not a Vector. Thus the statement 10.7 + vec2 is calling the __add__ method from the float class, and not from the Vector class. The built-in __add__ in the float class does not, of course, know what a Vector is, so it doesn’t know what to do when you try to add a Vector to a float.

What we need, therefore, is some method similar to __add__ that will look at the right operand rather than the left operand to determine which class’s method to call. Fortunately, there is such a method, called __radd__, where the extra ‘r’ stands for ‘right’. Here’s the new method:

class Vector(object):
"""a mathematical vector"""
def __init__(self, comps):
self.comps = comps

if isinstance(other, Vector):
if len(other.comps) == len(self.comps):
return Vector([self.comps[i] + other.comps[i]
for i in range(len(self.comps))])
else:
return None
elif isinstance(other, (int, float)):
self.comps += [other]
return self
else:
return None

if isinstance(other, (int, float)):
self.comps.insert(0, other)
return self


The __radd__ method is on lines 19 to 22. We need to be a bit careful in interpreting its arguments, as in this case, self refers to the right operand (even though it comes first in the argument list) and other refers to the left operand. Thus it is the right operand that calls the __radd__ method, with the left operand passed in as a parameter, which is just what we want. If we run this with the above code 10.7 + vec2 followed by print(vec2.comps), we find that vec2 is now [10.7, 5, 6, 7, 8], as required.

How does Python know when to call __add__ and when to call __radd__? The rule is that if the right operand is a Vector but the left operand is not, then __radd__ is called. If the left operand is a Vector, then __add__ is called, no matter what type the right operand is. So in the above code, __add__ is called for the case where we add two Vectors and when we add a Vector and a number, with the Vector on the left.

## In-place arithmetic operators

An in-place operator is one such as += which, for ints x and y and the expression x += y adds y to x and makes x point to the new value. In fact, the __add__ method above also works for +=, with the left operand passed as self and the right operand as other. The following code illustrates how this works:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

vec1 += vec2
print(vec1.comps)
print(vec2.comps)


vec1 now has comps of [6, 8, 10, 12] while vec2 remains unchanged.

What is effectively happening when the statement vec1 += vec2 is run is that the statement is rewritten as vec1 = vec1 + vec2, and then the overloaded __add__ is called to evaluate the right hand side, with the result then assigned to the left hand side, which is vec1 here.

Note that the statement vec1 += vec2 does not return anything, so you can’t say something like vec3 = vec1 += vec2. The value returned by the addition is assigned to vec1.

There is a specialized method called __iadd__ which can be overloaded for an in-place addition, but for most practical purposes, an ordinary __add__ method will suffice.

## Arithmetic operators and their methods

Here’s a table showing the most common arithmetic methods and their corresponding methods that can be overridden:

 Addition x + y x.__add__(y) Subtraction x - y x.__sub__(y) Multiplication x * y x.__mul__(y) Division x / y x.__truediv__(y) Floor (integer) division x // y x.__floordiv__(y) Power x ** y x.__pow__(y) Modulo (remainder) x % y x.__mod__(y)

All these methods work if the left operand is an object from the class in which the method is defined. If you want to have the right operand call the method, prefix the name with ‘r’, as above.

## Exercise

Extend the Vector class above by writing overloaded operators for multiplication, using the * operator.

If both operands are Vectors, the * operator should return the scalar product of the two Vectors. This is defined only for Vectors of the same dimension. If we have two vectors and , each with components, then the scalar product is defined as That is, multiply together the corresponding entries from the two vectors and add up the result. Note that the result is just a number, not a vector.

If only one operand (either right or left) is a Vector and the other operand is a number, then the Vector should have all its components multiplied by the number. In this case, the result is a Vector, and should be a new Vector, so that the original Vector should be unchanged.

The complete Vector class including the multiplication overloads is:

class Vector(object):
"""a mathematical vector"""
def __init__(self, comps):
self.comps = comps

if isinstance(other, Vector):
if len(other.comps) == len(self.comps):
return Vector([self.comps[i] + other.comps[i]
for i in range(len(self.comps))])
else:
return None
elif isinstance(other, (int, float)):
self.comps += [other]
return self
else:
return None

if isinstance(other, (int, float)):
self.comps.insert(0, other)
return self

def __mul__(self, other):
if isinstance(other, Vector):
if len(other.comps) == len(self.comps):
scalar = 0
for i in range(len(self.comps)):
scalar += self.comps[i] * other.comps[i]
return scalar
else:
return None
elif isinstance(other, (int, float)):
return Vector([other * self.comps[i]
for i in range(len(self.comps))])
else:
return None

def __rmul__(self, other):
if isinstance(other, (int, float)):
return self * other
return None



The __mul__ method on line 24 handles the cases where both operands are Vectors and where the left operand is a Vector and the right is a number. Lines 26 to 30 check that the Vectors are the same length and then calculate and return the scalar product. Lines 33 to 35 create and return a new Vector with its comps equal to the comps of the self Vector multiplied by other.

To handle the case where the left operand is a number and the right operand is a Vector, we overload the __rmul__ method on line 39. Since this operation should give the same answer as the case where the Vector and number are swapped, we can just return self * other, which, because the left operand is now a Vector, calls the overloaded __add__ method to calculate the result.

You can test the new methods with the code:

from Vector import Vector

vec1 = Vector([1.2,2,3,4])
vec2 = Vector([5,6,7,8])

scalar = vec1 * vec2
print(scalar)

vec6 = vec1 * 4.5
print(vec6.comps)

vec7 = 9.4 * vec1
print(vec7.comps)


This site uses Akismet to reduce spam. Learn how your comment data is processed.