Operator Overloading in Python

Operator Overloading #

Operator Overloading lets you redefine the meaning of operator respective to your class. It is the magic of operator overloading that we were able to use to + operator to add two numbers objects, as well as two string objects.

>>>
>>> 10 + 400
410
>>>
>>> "ten" + "sor"
'tensor'
>>>

Here + operator has two interpretations. When used with numbers it is interpreted as a addition operator whereas with strings it is interpreted as concatenation operator. In other words, we can say that + operator is overloaded for int class and str class.

So how do we redefine or overload an operator for a particular class ?

Operator Overloading is achieved by defining a special method in the class definition. The names of these methods starts and ends with double underscores (__). The special method used to overload + operator is called __add__(). Both int class and str class implements __add__() method. The int class version of the __add__() method simply adds two numbers whereas the str class version concatenates the string.

If the expression is for the form x + y, Python interprets it as x.__add__(y). The version of __add__() method called depends upon the type of x and y. If x and y are int objects then int class version of __add__() is called. On the other hand, if x and y are list objects then list class version of __add__() method is called.

>>>
>>> x, y = 10, 20
>>>
>>> x + y
30
>>> x.__add__(y)   # same as x + y
30
>>>
>>> x, y = [11, 22], [1000, 2000]
>>>
>>> x.__add__(y)  # same as x + y
[11, 22, 1000, 2000]
>>>

The following table lists operator and it's corresponding special method. Recall from lesson Objects and Classes in Python that the variables names which have two leading underscores are private variables, special methods listed in table are not private because they in addition to leading underscores they also have trailing underscores.

Operator Special Method Description
+ __add__(self, object) Addition
- __sub__(self, object) Subtraction
* __mul__(self, object) Multiplication
** __pow__(self, object) Exponentiation
/ __truediv__(self, object) Division
// __floordiv__(self, object) Integer Division
% __mod__(self, object) Modulus
== __eq__(self, object) Equal to
!= __ne__(self, object) Not equal to
> __gt__(self, object) Greater than
>= __ge__(self, object) Greater than or equal to
< __lt__(self, object) Less than
<= __le__(self, object) Less than or equal to
in __contains__(self, value) Membership operator
[index] __getitem__(self, index) Item at index
len() __len__(self) Calculate number of items
str() __str__(self) Convert object to a string

Notice that that last two items in table are not operators instead they are built-in functions. But if you want to use then with your class you should define their respective special methods.

The following program first perform an operation using an operator and then it performs the same operation using the corresponding special method.

python101/Chapter-17/special_methods.py

x, y = 2, 4

print("x = ", x, ", y =", y)

print("\nx + y =", x + y)
print("x.__add__(y) =", x.__add__(y))  # same as x + y

print("\nx * y = ", x * y)
print("x.__mul__(y) = ", x.__mul__(y))  # same as x * y

print("\nx / y = ", x / y)
print("x.__truediv__(y) = ", x.__truediv__(y))  # same as x / y

print("\nx ** y = ", x ** y)
print("x.__pow__(y) = ", x.__pow__(y))  # same as x ** y

print("\nx % y = ", x % y)
print("x.__mod__(y) = ", x.__mod__(y))  # same as x % y

print("\nx == y = ", x == y)
print("x.__eq__(y) = ", x.__eq__(y))  # same as x == y

print("\nx != y = ", x != y)
print("x.__ne__(y) = ", x.__ne__(y))  # same as x != y

print("\nx >= y = ", x >= y)
print("x.__ge__(y) = ", x.__ge__(y))  # same as x >= y

print("\nx <= y = ", x <= y)
print("x.__le__(y) = ", x.__le__(y))  # same as x <= y
print("------------------------------------------")

str1 = "special methods"

print("\nstr1 =", str1)

print("\n'ods' in str1 =", "ods" in str1)
print("str1.__contains__('ods') =", str1.__contains__("ods")) # same as "ods" in str1

print("\nlen(str1) =", len(str1))
print("str1.__len__() =", str1.__len__()) # same as len(str1)
print("------------------------------------------")

list1 = [11,33, 55]

print("\nlist1 =", list1)

print("\nlist1[1] =", list1[1])
print("list1.__getitem(1) =", list1.__getitem__(1)) # same as list1[1]
print("str(list1) =",str(list1))  # same as list1.__str__()

Output:

x =  2 , y = 4

x + y = 6
x.__add__(y) = 6

x * y =  8
x.__mul__(y) =  8

x / y =  0.5
x.__truediv__(y) =  0.5

x ** y =  16
x.__pow__(y) =  16

x % y =  2
x.__mod__(y) =  2

x == y =  False
x.__eq__(y) =  False

x != y =  True
x.__ne__(y) =  True

x >= y =  False
x.__ge__(y) =  False

x <= y =  True
x.__le__(y) =  True
------------------------------------------

str1 = special methods

'ods' in str1 = True
str1.__contains__('ods') = True

len(str1) = 15
str1.__len__() = 15
------------------------------------------

list1 = [11, 33, 55]

list1[1] = 33
list1.__getitem(1) = 33
str(list1) = [11, 33, 55]

The following program demonstrates how we can override operators in a class.

python101/Chapter-17/point.py

import math

class Point:

    def __init__(self, x=0, y=0):
        self.__x = x
        self.__y = y   

    # get the x coordinate
    def get_x(self):
        return self.__x

    # set the x coordinate
    def set_x(self, x):
        self.__x = x

    # get the y coordinate
    def get_y(self):
        return self.__y

    # set the y coordinate
    def set_y(self, y):
        self.__y = y

    # get the current position
    def get_position(self):
        return self.__x, self.__y

    # change x and y coordinate by a and b
    def move(self, a, b):
        self.__x += a
        self.__y += b

    # overloading + operator
    def __add__(self, point_obj):
        return Point(self.__x + point_obj.__x, self.__y + point_obj.__y)

    # overloading - operator
    def __sub__(self, point_obj):
        return Point(self.__x - point_obj.__x, self.__y - point_obj.__y)

    # overloading < operator
    def __lt__(self, point_obj):
        return math.sqrt(self.__x ** 2 + self.__y ** 2) < math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)

    # overloading <= operator
    def __le__(self, point_obj):
        return math.sqrt(self.__x ** 2 + self.__y ** 2) <= math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)

    # overloading > operator
    def __gt__(self, point_obj):
        return math.sqrt(self.__x ** 2 + self.__y ** 2) > math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)

    # overloading >= operator
    def __ge__(self, point_obj):
        return math.sqrt(self.__x ** 2 + self.__y ** 2) >= math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)

    # overloading == operator
    def __eq__(self, point_obj):
        return math.sqrt(self.__x ** 2 + self.__y ** 2) == math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)

    ## overriding __str__ function
    def __str__(self):
        return "Point object is at: (" + str(self.__x) + ", " + str(self.__y) + ")"


p1 = Point(4, 6)
p2 = Point(10, 6)

print("Is p1 < p2 ?", p1 < p2)   # p1 < p2 is equivalent to p1.__lt__(p2)
print("Is p1 <= p2 ?", p1 <= p2)  # p1 <= p2 is equivalent to p1.__le__(p2)
print("Is p1 > p2 ?", p1 > p2)   # p1 > p2 is equivalent to p1.__gt__(p2)
print("Is p1 >= p2 ?", p1 >= p2)   # p1 >= p2 is equivalent to p1.__ge__(p2)
print("Is p1 == p2 ?", p1 == p2)   # p1 < p2 is equivalent to p1.__eq__(p2)

p3 = p1 + p2  # p1 + p2 is equivalent to p1.__add__(p2)
p4 = p1 - p2  # p1 - p2 is equivalent to p1.__sub__(p2)

print()  # print an empty line
print(p1)  # print(p1) is equivalent to print(p1.__str__())
print(p2)  # print(p2) is equivalent to print(p2.__str__())
print(p3)  # print(p3) is equivalent to print(p3.__str__())
print(p4)  # print(p4) is equivalent to print(p4.__str__())

Output:

Is p1 < p2 ? True
Is p1 <= p2 ? True
Is p1 > p2 ? False
Is p1 >= p2 ? False
Is p1 == p2 ? False

Point object is at: (4, 6)
Point object is at: (10, 6)
Point object is at: (14, 12)
Point object is at: (-6, 0)

The Point class defines two private attributes __x and __y which represent x and y coordinates in a plane. Then it defines getter and setter methods for those attributes. It also defines, get_position() and move() method to get the current position and change coordinates respectively.

In line 35, we have overloaded + operator for the Point class. The __add__() method creates a new Point object by adding individual coordinates of one Point object to another Point object. Finally, it returns the newly created object to it's caller. This allows us to write expressions like:

p3 = p1 + p2

where p1, p2 and p3 are three Point objects.

Python interprets the above expression as p3 = p1.__add__(p2), and calls the __add__() method to add two Point objects. The return value from the __add__() method is then assigned to p3. It is important to note that when __add__() method is called, the value of p1 is assigned to the self parameter and the value of p2 is assigned to the point_obj parameter. The rest of the special methods works in the similar fashion.