How to Define Operator Overloading in Python Classes
Operator overloading allows user-defined objects to interact with built-in operators like +, -, *, ==, and even []. This makes custom classes feel like native Python types, improving code readability and expressiveness. For example, instead of vector.add(other), you can simply write vector + other.
This guide explains how to overload operators using special "magic" methods (dunder methods).
Understanding Magic Methods
Magic methods are special methods surrounded by double underscores (__method__). Python calls these methods implicitly when you perform operations on objects.
+calls__add__==calls__eq__print()calls__str__
Arithmetic Operators (+, -, *)
To support addition, implement __add__. This method takes self and other as arguments and should return a new instance of the class (or a different type if appropriate).
Example: Vector Addition
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# ✅ Overloading the '+' operator
def __add__(self, other):
# Check if 'other' is also a Vector to ensure compatibility
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
# Handle other types or raise error (e.g., adding a scalar)
return NotImplemented
# To visualize the result nicely
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 4)
v2 = Vector(1, 3)
# Calls v1.__add__(v2)
v3 = v1 + v2
print(v3)
Output:
Vector(3, 7)
If __add__ returns NotImplemented, Python will try to call the reverse method on the other operand (other.__radd__). If that also fails, a TypeError is raised.
Comparison Operators (==, <, >)
To allow objects to be compared or sorted, implement methods like __eq__ (equality) and __lt__ (less than).
Example: Comparing Rectangles by Area
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
# ✅ Overloading '=='
def __eq__(self, other):
if isinstance(other, Rectangle):
return self.area == other.area
return False
# ✅ Overloading '<' (Less Than)
def __lt__(self, other):
if isinstance(other, Rectangle):
return self.area < other.area
return NotImplemented
r1 = Rectangle(3, 4) # Area 12
r2 = Rectangle(2, 6) # Area 12
r3 = Rectangle(5, 5) # Area 25
print(f"r1 == r2? {r1 == r2}")
print(f"r1 < r3? {r1 < r3}")
Output:
r1 == r2? True
r1 < r3? True
Implementing __eq__ automatically enables !=. Implementing __lt__ and using the @functools.total_ordering decorator can automatically generate __le__, __gt__, and __ge__.
String Representation (str, repr)
While not strictly "operators," str() and repr() are fundamental for debugging and displaying objects.
__str__: User-friendly string representation (used byprint()).__repr__: Unambiguous representation (used in the interactive console/debugger), ideally valid Python code to recreate the object.
class Money:
def __init__(self, amount, currency="USD"):
self.amount = amount
self.currency = currency
# ✅ For end-users
def __str__(self):
return f"{self.amount:.2f} {self.currency}"
# ✅ For developers
def __repr__(self):
return f"Money({self.amount}, '{self.currency}')"
cash = Money(50.5)
print(str(cash)) # Uses __str__
print(repr(cash)) # Uses __repr__
Output:
50.50 USD
Money(50.5, 'USD')
Conclusion
To define operator overloading in Python:
- Arithmetic: Implement
__add__,__sub__,__mul__, etc. Return a new object. - Comparison: Implement
__eq__,__lt__, etc. Return a boolean. - Display: Implement
__str__and__repr__for readable output. - Type Checking: Use
isinstance()inside magic methods to handle incompatible types gracefully or returnNotImplemented.