Skip to main content

Python Operator Overloading

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.

note

Special functions are called automatically when certain operations are performed on instances of the class.

Some special functions are:

FunctionDescription
__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.

OperatorExpressionInternallySpecial Function
Additionp1 + p2p1.__add__(p2)__add__(self, other)
Subtractionp1 - p2p1.__sub__(p2)__sub__(self, other)
Multiplicationp1 * p2p1.__mul__(p2)__mul__(self, other)
Exponentiationp1 ** p2p1.__pow__(p2)__pow__(self, other)
Divisionp1 / p2p1.__truediv__(p2)__truediv__(self, other)
Floor Divisionp1 // p2p1.__floordiv__(p2)__floordiv__(self, other)
Remainder (modulo)p1 % p2p1.__mod__(p2)__mod__(self, other)
Bitwise Left Shiftp1 << p2p1.__lshift__(p2)__lshift__(self, other)
Bitwise Right Shiftp1 >> p2p1.__rshift__(p2)__rshift__(self, other)
Bitwise ANDp1 & p2p1.__and__(p2)__and__(self, other)
Bitwise ORp1 \ p2p1.__or__(p2)__or__(self, other)
Bitwise XORp1 ^ p2p1.__xor__(p2)__xor__(self, other)
Bitwise NOT~p1p1.__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)

# Usage
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")

# Usage
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.

OperatorExpressionInternallySpecial Function
Less Thanp1 < p2p1.__lt__(p2)__lt__(self, other)
Less Than Or Equal Top1 <= p2p1.__le__(p2)__le__(self, other)
Equal Top1 == p2p1.__eq__(p2)__eq__(self, other)
Not Equal Top1 != p2p1.__ne__(p2)__ne__(self, other)
Greater Thanp1 > p2p1.__gt__(p2)__gt__(self, other)
Greater Than Or Equal Top1 >= p2p1.__ge__(p2)__ge__(self, other)

For example:

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

# Overload < operator
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:

OperatorExpressionInternallySpecial Function
Representationrepr(obj)obj.__repr__()__repr__(self)
String Conversionstr(obj)obj.__str__()__str__(self)
Lengthlen(obj)obj.__len__()__len__(self)
Membership Testitem in objobj.__contains__(item)__contains__(self, item)
Iteration Protocolfor i in obj:obj.__iter__()__iter__(self)
Item Accessobj[index]obj.__getitem__(index)__getitem__(self, index)
Item Assignmentobj[index] = valueobj.__setitem__(index, value)__setitem__(self, index, value)
Item Deletiondel obj[index]obj.__delitem__(index)__delitem__(self, index)
Callableobj(*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.