Skip to main content

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)
note

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
tip

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 by print()).
  • __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:

  1. Arithmetic: Implement __add__, __sub__, __mul__, etc. Return a new object.
  2. Comparison: Implement __eq__, __lt__, etc. Return a boolean.
  3. Display: Implement __str__ and __repr__ for readable output.
  4. Type Checking: Use isinstance() inside magic methods to handle incompatible types gracefully or return NotImplemented.