1. Introduction
Operator overloading is the ability to redefine what an operator like +, -, *, ==, or < does when applied to instances of a custom class. Python doesn't let you invent new operator symbols, but it lets you give existing operators new, class-specific meaning by implementing the corresponding dunder method — for example, a + b on custom objects calls a.__add__(b) behind the scenes.
Cricket analogy: Python won't let you invent a new symbol like a special 'boundary' operator, but it lets + mean something new for a custom 'Score' class -- score_a + score_b calls score_a.__add__(score_b) to combine two innings totals behind the scenes.
This is widely used for mathematical types (vectors, matrices, complex numbers, money), and also for comparison-based types that need custom ordering (e.g. sorting custom priority objects) or custom equality (e.g. comparing domain objects by value rather than identity).
Cricket analogy: This is like using overloading for a 'RunRate' type doing math, and also for a 'PlayerRanking' type that needs custom ordering by strike rate, or custom equality comparing two players by their stats rather than by which object they are.
2. Syntax
class MyNumber:
def __init__(self, value):
self.value = value
def __add__(self, other):
if not isinstance(other, MyNumber):
return NotImplemented
return MyNumber(self.value + other.value)
def __radd__(self, other):
# supports other + self when other doesn't know how to add MyNumber
return self.__add__(other)
def __lt__(self, other):
return self.value < other.value
def __eq__(self, other):
return isinstance(other, MyNumber) and self.value == other.value3. Explanation
Each operator maps to a specific dunder method: + -> __add__, - -> __sub__, * -> __mul__, / -> __truediv__, == -> __eq__, != -> __ne__, < -> __lt__, <= -> __le__, > -> __gt__, >= -> __ge__. When you write a + b, Python first tries a.__add__(b); if that returns NotImplemented (e.g. because b's type isn't supported), Python tries b.__radd__(a) — the 'reflected' method — before finally raising TypeError if neither succeeds.
Cricket analogy: Comparing two batters' averages with > maps to __gt__; if player_a > player_b returns NotImplemented because player_b isn't a comparable type, Python tries player_b.__lt__(player_a) before raising TypeError.
The functools.total_ordering class decorator can reduce boilerplate: define __eq__ and just one of __lt__/__le__/__gt__/__ge__, and it fills in the rest. This is handy for classes that need full ordering support without hand-writing all six comparison dunders.
Gotcha: always return NotImplemented (a special singleton), never raise an exception directly and never return False/None, when an operator method doesn't know how to handle the other operand's type. Returning NotImplemented lets Python try the reflected method on the other object; if you instead return False from __eq__ for an incompatible type, you might get logically wrong results (e.g. a MyNumber accidentally comparing equal to an unrelated object because both a.__eq__ and Python's fallback identity check disagree in confusing ways). If you outright raise, you break Python's protocol for trying the reflected operator.
4. Example
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
if not isinstance(scalar, (int, float)):
return NotImplemented
return Vector(self.x * scalar, self.y * scalar)
__rmul__ = __mul__
def __eq__(self, other):
return isinstance(other, Vector) and self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x ** 2 + self.y ** 2) < (other.x ** 2 + other.y ** 2)
v1 = Vector(2, 3)
v2 = Vector(1, 1)
print(v1 + v2)
print(v1 - v2)
print(v1 * 3)
print(2 * v1)
print(v1 == Vector(2, 3))
print(v2 < v1)5. Output
Vector(3, 4)
Vector(1, 2)
Vector(6, 9)
Vector(4, 6)
True
True6. Key Takeaways
- Each Python operator maps to a specific dunder method, e.g. + -> __add__, == -> __eq__, < -> __lt__.
- Return NotImplemented (not False, None, or an exception) when an operand type isn't supported, so Python can try the reflected method.
- Reflected methods (__radd__, __rmul__, ...) handle cases where the left operand doesn't know how to combine with your type.
- functools.total_ordering can auto-generate the remaining comparison methods from __eq__ plus one ordering method.
- Operator overloading should preserve intuitive semantics — don't make + do something unrelated to addition.
- Assigning __rmul__ = __mul__ is a common shortcut when the operation is commutative (a * b == b * a).
Practice what you learned
1. Which dunder method is called when you write a + b for two custom objects?
2. What should __add__ return if the other operand's type is not supported?
3. In the Vector example, what does 2 * v1 evaluate to, given __rmul__ = __mul__ and v1 = Vector(2, 3)?
4. Which decorator can auto-generate the remaining ordering methods (like __le__, __gt__, __ge__) if you define __eq__ and __lt__?
5. Why is it problematic for __eq__ to return False (instead of NotImplemented) when comparing against an unsupported type?
6. Which operator does __truediv__ correspond to?
Was this page helpful?
You May Also Like
Magic (Dunder) Methods in Python
How Python's double-underscore methods let your classes plug into built-in syntax like print(), ==, len(), and hashing.
Class Methods and Static Methods in Python
The difference between instance methods, @classmethod (receives cls), and @staticmethod (receives neither self nor cls), with practical use cases.
Classes and Objects in Python
How Python's class statement defines a blueprint for objects, and how instances are created and used.
Encapsulation in Python
How Python bundles data and methods together and uses naming conventions and name mangling to signal restricted access.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics