Skip to content

Python Operator Overloading

How to customize arithmetic, bitwise, and comparison operators for Python classes using special methods like __add__, __lt__, and __str__ for intuitive, domain-specific behavior.

In Python, Operator Overloading allows you to redefine how operators work for user-defined types. This means that the same operator can perform different actions depending on the types of the operands.

For instance, the + operator can be used to add two integers, concatenate two strings, or merge two lists. This is possible because the + operator is overloaded by the int, str, and list classes to perform these specific actions

Python Operator Overloading and Special Functions

Special functions in Python (also known as magic methods) are predefined methods with double underscores at the beginning and end of their names, like __init__() or __str__(). They are used to implement operator overloading and other special behaviors for user-defined classes.

Some special functions are:

Function Description
__init__() Initializes an object after it has been created. It is called when an object is instantiated
__str__() Returns a string representation of an object. It is used when you try to convert an object to a string using str()
__len__() Provides the length of an object when the len() function is applied to it
__add__() Defines the behavior of the + operator for instances of the class
__call__() Calls objects of the class like a normal function

It is indeed possible to overload arithmetic, bitwise, and comparison operators.

Overloading Arithmetic and Bitwise Operators

The following table contains Arithmetic and Bitwise Operators that you can overload.

Operator Expression Internally Special Function
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)
Exponentiation p1 ** p2 p1.__pow__(p2) __pow__(self, other)
Division p1 / p2 p1.__truediv__(p2) __truediv__(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 \ p2 p1.__or__(p2) __or__(self, other)
Bitwise XOR p1 ^ p2 p1.__xor__(p2) __xor__(self, other)
Bitwise NOT ~p1 p1.__invert__() __invert__(self)

For example:

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

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

c1 = MyArithmeticClass(1,  2)
c2 = MyArithmeticClass(2,  3)
c3 = c1 + c2  # Calls __add__ method
class MyBitwiseClass:
    def __init__(self, value):
        self.value = value

    def __and__(self, obj):
        if isinstance(obj, MyBitwiseClass):
            return MyBitwiseClass(self.value & obj.value)
        else:
            raise ValueError("Must be an object of class MyBitwiseClass")

b1 = MyBitwiseClass(10)
b2 = MyBitwiseClass(12)
result = b1 & b2  # Calls __and__ method

Overloading Comparison Operators

The following table contains Comparison Operators that you can overload.

Operator Expression Internally Special Function
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) __ge__(self, other)

For example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __lt__(self, other):
        if isinstance(other, Person):
            return self.age < other.age
        else:
            raise TypeError("Can only compare to another Person instance")

p1 = Person("Alice",  20)
p2 = Person("Bob",  30)

print(p1 < p2)  # Prints True, because Alice is younger than Bob

Overloading of other Operators

Other operators that you can overload:

Operator Expression Internally Special Function
Representation repr(obj) obj.__repr__() __repr__(self)
String Conversion str(obj) obj.__str__() __str__(self)
Length len(obj) obj.__len__() __len__(self)
Membership Test item in obj obj.__contains__(item) __contains__(self, item)
Iteration Protocol for i in obj: obj.__iter__() __iter__(self)
Item Access obj[index] obj.__getitem__(index) __getitem__(self, index)
Item Assignment obj[index] = value obj.__setitem__(index, value) __setitem__(self, index, value)
Item Deletion del obj[index] obj.__delitem__(index) __delitem__(self, index)
Callable obj(*args, **kwargs) obj.__call__(*args, **kwargs) __call__(self, *args, **kwargs)

Advantages and Disadvantages of Operator Overloading

Advantages of Operator Overloading in Python:

  • Code Readability: Operator overloading allows the use of familiar operators with user-defined types, making code more intuitive and easier to read.
  • Consistency: Objects of a class behave consistently with built-in types, which is beneficial for developers who are accustomed to how built-in types work.
  • Simplicity: It simplifies code by providing a way to perform common operations without having to call explicit methods.
  • Code Reuse: By implementing one operator method, you can define the behavior for other operators as well, reducing code duplication.
  • Domain Specificity: Operator overloading enables the use of notation that closely matches the problem domain, which can improve the clarity of the code.
  • Built-in Function Integration: Operator overloading allows custom classes to work seamlessly with built-in functions like len(), abs(), etc., thus extending the functionality of these built-in functions.

Disadvantages of Operator Overloading in Python:

  • Unexpected Behavior: Overloaded operators might lead to unexpected behavior if not implemented correctly, potentially causing confusion for those reading or maintaining the code.
  • Limited Operators: Only pre-existing operators can be overloaded; new operators cannot be introduced.
  • Unchanged Precedence and Arity: The precedence and arity of existing operators cannot be altered, which may limit the flexibility of operator overloading.
  • Maintenance Challenges: Overloading operators can increase the complexity of the codebase, making it harder to debug and maintain.
  • Confusion with Built-in Types: Overloading operators too closely to match built-in types can lead to confusion if the custom implementation differs significantly from the expected behavior of the built-in operators.
Navigation