Python Operator Overloading#

You can change the meaning of an operator in Python depending upon the operands used. In this tutorial, you will learn how to use operator overloading in Python Object Oriented Programming.

Python Operator Overloading#

Python operators work for built-in classes. But the same operator behaves differently with different types. For example, the + operator will perform arithmetic addition on two numbers, merge two lists, or concatenate two strings.

This feature in Python that allows the same operator to have different meaning according to the context is called operator overloading.

Python Operator overloading allows us to use mathematical, logical, and bitwise operators on Python objects.

So what happens when we use them with objects of a user-defined class? Let us consider the following class, which tries to simulate a point in 2-D coordinate system.

# Example 1: error

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

p1 = Point(1, 2)
p2 = Point(2, 3)
print(p1+p2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-5f195de51d93> in <module>
      8 p1 = Point(1, 2)
      9 p2 = Point(2, 3)
---> 10 print(p1+p2)

TypeError: unsupported operand type(s) for +: 'Point' and 'Point'

Explanation:

Here, we can see that a TypeError was raised, since Python didn’t know how to add two Point objects together.

However, we can achieve this task in Python through operator overloading. But first, let’s get a notion about special functions.

Python Special Functions#

Class functions that begin with double underscore __ are called special functions in Python.

These functions are not the typical functions that we define for a class. The __init__() function we defined above is one of them. It gets called every time we create a new object of that class.

There are numerous other special functions in Python. Visit Python Special Functions to learn more about them.

Using special functions, we can make our class compatible with built-in functions.

p1 = Point(2,3)
print(p1)
<__main__.Point object at 0x00000218FFC65220>

Suppose we want the print() function to print the coordinates of the Point object instead of what we got. We can define a __str__() method in our class that controls how the object gets printed. Let’s look at how we can achieve this:

class Point:
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
    
    def __str__(self):
        return "({0},{1})".format(self.x,self.y)

Now let’s try the print() function again.

# Example 1: without error

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return "({0}, {1})".format(self.x, self.y)


p1 = Point(2, 3)
print(p1)
(2, 3)

That’s better. Turns out, that this same method is invoked when we use the built-in function str() or format().

str(p1)
'(2, 3)'
format(p1)
'(2, 3)'

So, when you use str(p1) or format(p1), Python internally calls the p1.__str__() method. Hence the name, special functions.

Now let’s go back to operator overloading.

# Example 2:

class Book:
    def __init__(self, pages):
        self.pages = pages

    def __add__(self, other):
        return self.pages + other.pages

b1 = Book(150)
b2 = Book(100)
print("Total Number of pages:", b1 + b2)
# Output Total Number of pages: 250
Total Number of pages: 250

Explanation:

In the above example, we overload the + operator internally + operator implemented using def __add__(self, other) method. This method is known as a magic method.

# Example 3:

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):
        return 'Vector (%d, %d)' % (self.a, self.b)

    def __add__(self,other):
        return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
Vector (7, 8)

Overloading the + Operator#

To overload the + operator, we will need to implement __add__() function in the class. With great power comes great responsibility. We can do whatever we like, inside this function. But it is more sensible to return a Point object of the coordinate sum.

>>> class Point:
>>>    def __init__(self, x=0, y=0):
>>>        self.x = x
>>>        self.y = y

>>>    def __str__(self):
>>>        return "({0},{1})".format(self.x, self.y)

>>>    def __add__(self, other):
>>>        x = self.x + other.x
>>>        y = self.y + other.y
>>>        return Point(x, y)

Now let’s try the addition operation again:

# Example 1:

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return "({0},{1})".format(self.x, self.y)

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Point(x, y)


p1 = Point(1, 2)
p2 = Point(2, 3)

print(p1+p2)
(3,5)

Explanation:

What actually happens is that, when you use p1 + p2, Python calls p1.__add__(p2) which in turn is Point.__add__(p1,p2). After this, the addition operation is carried out the way we specified.

In Python, there are different magic methods available to perform overloading operations. The below table shows the magic methods’ names to overload the mathematical operator, assignment operator, and relational operators in Python.

Operator

Expression

Internally

Magic method

Addition

p1 + p2

p1.__add__(p2)

__add__(self, other)

Subtraction

p1 - p2

p1.__sub__(p2)

__sub__(self, other)

Multiplication

p1 * p2

p1.__mul__(p2)

__mul__(self, other)

Power

p1 ** p2

p1.__pow__(p2)

__pow__(self, other)

Increment

p1 += p2

p1.__iadd__(p2)

__iadd__(self, other)

Decrement

p1 -= p2

p1.__isub__(p2)

__isub__(self, other)

Product

p1 *= p2

p1.__imul__(p2)

__imul__(self, other)

Division

p1 /= p2

p1.__idiv__(p2)

__idiv__(self, other)

Modulus

p1 %= p2

p1.__imod__(p2)

__imod__(self, other)

Power

p1 **= p2

p1.__ipow__(p2)

__ipow__(self, other)

Division

p1 / p2

p1.__truediv__(p2)

__div__(self, other)

Floor Division

p1 // p2

p1.__floordiv__(p2)

__floordiv__(self,other)

Remainder (modulo)

p1 % p2

p1.__mod__(p2)

__mod__(self, other)

Bitwise Left Shift

p1 << p2

p1.__lshift__(p2)

__lshift__(self, other)

Bitwise Right Shift

p1 >> p2

p1.__rshift__(p2)

__rshift__(self, other)

Bitwise AND

p1 & p2

p1.__and__(p2)

__and__(self, other)

Bitwise OR

p1 I p2

p1.__or__(p2)

__or__(self, other)

Bitwise XOR

p1 ^ p2

p1.__xor__(p2)

__xor__(self, other)

Bitwise NOT

~p1

p1.__invert__()

__invert__(self)

Overloading Comparison Operators#

Python does not limit operator overloading to arithmetic operators only. We can overload comparison operators as well.

Suppose we wanted to implement the less than symbol < symbol in our Point class.

Let us compare the magnitude of these points from the origin and return the result for this purpose. It can be implemented as follows.

# Example 1: overloading the less than operator
class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return "({0},{1})".format(self.x, self.y)

    def __lt__(self, other):
        self_mag = (self.x ** 2) + (self.y ** 2)
        other_mag = (other.x ** 2) + (other.y ** 2)
        return self_mag < other_mag

p1 = Point(1,1)
p2 = Point(-2,-3)
p3 = Point(1,-1)

# use less than
print(p1<p2)
print(p2<p3)
print(p1<p3)
True
False
False

Similarly, the special functions that we need to implement, to overload other comparison operators are tabulated below.

Operator

Expression

Internally

Magic method

Less than

p1 < p2

p1.__lt__(p2)

__lt__(self, other)

Less than or equal to

p1 <= p2

p1.__le__(p2)

__le__(self, other)

Equal to

p1 == p2

p1.__eq__(p2)

__eq__(self, other)

Not equal to

p1 != p2

p1.__ne__(p2)

__ne__(self, other)

Greater than

p1 > p2

p1.__gt__(p2)

__gt__(self, other)

Greater than or equal to

p1 >= p2

p1.__ge__(p2)

__gt__(self, other)