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 def __add__(self, other): 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 def __add__(self, other): 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 def __add__(self, other): 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 def __radd__(self, other): 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 def __add__(self, other): 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 def __radd__(self, other): 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)