Skip to content

Easily Master Python Basics

[

Operator and Function Overloading in Custom Python Classes

By Malay Agarwal

If you’ve used the + or * operator on a str object in Python, you must have noticed its different behavior when compared to int or float objects:

# Adds the two numbers
1 + 2
# Output: 3
# Concatenates the two strings
'Real' + 'Python'
# Output: 'RealPython'
# Gives the product
3 * 2
# Output: 6
# Repeats the string
'Python' * 3
# Output: 'PythonPythonPython'

You might have wondered how the same built-in operator or function shows different behavior for objects of different classes. This is called operator overloading or function overloading respectively. This article will help you understand this mechanism so that you can do the same in your own Python classes and make your objects more Pythonic.

The Python Data Model

Say you have a class representing an online order having a cart (a list) and a customer (a str or instance of another class which represents a customer).

In such a case, it is quite natural to want to obtain the length of the cart list. Someone new to Python might decide to implement a method called get_cart_len() in their class to do this. But you can configure the built-in len() in such a way that it returns the length of the cart list when given our object.

In another case, we might want to append something to the cart. Again, someone new to Python would think of implementing a method called append_to_cart() that takes an item and appends it to the cart list. But you can configure the + operator in such a way that it appends a new item to the cart.

Python does all this using special methods. These special methods are predefined methods in Python that allow you to customize the behavior of built-in operators and functions for your classes. Here are some of the common special methods used for overloading operators and functions in Python:

Overloading Built-in Functions

Giving a Length to Your Objects Using len()

The len() function in Python returns the number of elements in an object. By overloading this function using the __len__() special method, you can customize how your object responds to the len() function.

class Cart:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
cart = Cart(['item1', 'item2', 'item3'])
print(len(cart))
# Output: 3

Making Your Objects Work With abs()

The abs() function in Python returns the absolute value of a number. By overloading this function using the __abs__() special method, you can customize how your object responds to the abs() function.

class ComplexNumber:
def __init__(self, real, imaginary):
self.real = real
self.imaginary = imaginary
def __abs__(self):
return (self.real ** 2 + self.imaginary ** 2) ** 0.5
complex_num = ComplexNumber(3, 4)
print(abs(complex_num))
# Output: 5.0

Printing Your Objects Prettily Using str()

The str() function in Python returns a string representation of an object. By overloading this function using the __str__() special method, you can customize how your object is printed.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Name: {self.name}, Age: {self.age}"
person = Person("Alice", 25)
print(str(person))
# Output: Name: Alice, Age: 25

Representing Your Objects Using repr()

The repr() function in Python returns a string representation of an object that can be used to recreate the object. By overloading this function using the __repr__() special method, you can customize how your object is represented.

class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
point = Point(2, 3)
print(repr(point))
# Output: Point(2, 3)

Making Your Objects Truthy or Falsey Using bool()

The bool() function in Python returns True or False depending on whether the given object is considered truthy or falsey. By overloading this function using the __bool__() special method, you can customize how your object is evaluated in a boolean context.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __bool__(self):
return self.age > 18
person = Person("Bob", 17)
print(bool(person))
# Output: False
person = Person("Charlie", 20)
print(bool(person))
# Output: True

Overloading Built-in Operators

Making Your Objects Capable of Being Added Using +

The + operator in Python is used for addition when applied to numbers, but it is also used for concatenation when applied to strings or lists. By overloading the + operator using the __add__() special method, you can customize how your object responds to the + operator.

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
result = v1 + v2
print(result.x, result.y)
# Output: 4 6

Shortcuts: the += Operator

The += operator in Python is equivalent to x = x + y. By overloading the += operator using the __iadd__() special method, you can customize how your object responds to the += operator.

class Counter:
def __init__(self, count):
self.count = count
def __iadd__(self, other):
self.count += other
return self
counter = Counter(5)
counter += 3
print(counter.count)
# Output: 8

Indexing and Slicing Your Objects Using []

The indexing and slicing operators [] are used to access elements of objects like lists, tuples, or strings. By overloading the [] operator using the __getitem__() and __setitem__() special methods, you can customize how your object is accessed and modified using indexing and slicing.

class MyList:
def __init__(self):
self.elements = []
def __getitem__(self, index):
return self.elements[index]
def __setitem__(self, index, value):
self.elements[index] = value
my_list = MyList()
my_list.elements = [1, 2, 3, 4, 5]
print(my_list[2])
# Output: 3
my_list[2] = 9
print(my_list.elements)
# Output: [1, 2, 9, 4, 5]

Reverse Operators: Making Your Classes Mathematically Correct

When you perform operations like addition or multiplication, Python uses the special methods __add__(), __radd__(), __mul__(), and __rmul__(). The __add__() and __mul__() methods handle the cases where the left operand is an instance of your class, and the __radd__() and __rmul__() methods handle the cases where the right operand is an instance of your class.

class Number:
def __init__(self, value):
self.value = value
def __add__(self, other):
if isinstance(other, Number):
return Number(self.value + other.value)
else:
return Number(self.value + other)
def __radd__(self, other):
return self.__add__(other)
def __mul__(self, other):
if isinstance(other, Number):
return Number(self.value * other.value)
else:
return Number(self.value * other)
def __rmul__(self, other):
return self.__mul__(other)
num1 = Number(5)
num2 = Number(3)
result = num1 + num2
print(result.value)
# Output: 8
result = num1 + 2
print(result.value)
# Output: 7
result = 2 + num1
print(result.value)
# Output: 7
result = num1 * num2
print(result.value)
# Output: 15
result = num1 * 2
print(result.value)
# Output: 10
result = 2 * num1
print(result.value)
# Output: 10

Recap and Resources

In this article, you learned how to overload operators and functions in Python classes to customize their behavior. By using special methods, you can make your classes work with built-in operators and functions in a way that makes them more Pythonic.

Python’s data model and special methods provide a powerful and flexible way to define the behavior of your own classes. By understanding and utilizing these concepts, you can write more expressive and intuitive Python code.

To learn more about operator and function overloading in Python, check out the following resources:

Now that you have a better understanding of operator and function overloading in Python, you can apply these concepts to your own classes and make your code more elegant and readable. So go ahead and start creating more Pythonic objects!

*[OOP]: Object-Oriented Programming