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.