Operator Overloading
Operator Overloading
Operator overloading allows user-defined classes to intercept and define custom semantics for Python's built-in operators—such as arithmetic symbols (+, -, *, /), matrix multiplication (@), augmented assignment (+=, -=), and rich comparison operators (==, <, >=).
Through operator overloading, your custom classes can behave as intuitive, first-class mathematical entities, domain models, or fluent query builders.
1. The Operator Dispatch Architecture & NotImplemented
When Python encounters a binary expression like a + b, it dispatches the operation through a multi-stage protocol:
The Difference: NotImplemented vs NotImplementedError
__add__, __eq__, __mul__), if your class does not support the type of the operand, you must return the singleton NotImplemented, NOT raise NotImplementedError.- Returning
NotImplemented: Tells the Python runtime, "I don't know how to handle this operand. Please check the right-hand operand's reflected method (e.g.,__radd__) or fallback comparison." - Raising
NotImplementedError: Immediately terminates the expression with an uncaught exception, preventing the other operand from handling the operation.
2. Arithmetic and Reflected Operators
Arithmetic operators come in two primary flavors: normal (left-hand) and reflected/reverse (right-hand, prefixed with r).
| Operator | Left-hand Method | Reflected Method | In-place Method |
|---|---|---|---|
+ | __add__(self, other) | __radd__(self, other) | __iadd__(self, other) |
- | __sub__(self, other) | __rsub__(self, other) | __isub__(self, other) |
* | __mul__(self, other) | __rmul__(self, other) | __imul__(self, other) |
/ | __truediv__(self, other) | __rtruediv__(self, other) | __itruediv__(self, other) |
// | __floordiv__(self, other) | __rfloordiv__(self, other) | __ifloordiv__(self, other) |
% | __mod__(self, other) | __rmod__(self, other) | __imod__(self, other) |
** | __pow__(self, other) | __rpow__(self, other) | __ipow__(self, other) |
@ | __matmul__(self, other) | __rmatmul__(self, other) | __imatmul__(self, other) |
Implementing Arithmetic with Scalars and Objects
Visual Architecture & Process Flow
How data and code flow step-by-step
3. In-Place Augmented Assignment (+=, -=, *=)
When you define methods like __iadd__, Python invokes them during a += b.
- Mutable objects (like
list):__iadd__modifiesselfin-place and returnsself. - Immutable objects (like
int,str): If__iadd__is omitted, Python falls back toa = a + b, allocating a new instance.
4. Rich Comparison Operators and total_ordering
Python defines 6 rich comparison dunder methods:
| Comparison | Method |
|---|---|
== | __eq__(self, other) |
!= | __ne__(self, other) |
< | __lt__(self, other) |
<= | __le__(self, other) |
> | __gt__(self, other) |
>= | __ge__(self, other) |
Instead of manually implementing all 6 operators, the standard library provides @functools.total_ordering. You only need to define __eq__ and one ordering method (__lt__, __le__, __gt__, or __ge__), and Python automatically synthesizes the remaining 4 operators.
Visual Architecture & Process Flow
How data and code flow step-by-step
5. Summary and Architectural Rules
- 1Always return
NotImplementedon unknown operand types: This allows Python to query the other operand or produce clear, standardized error messages. - 2In-place methods must return
self: Failing to returnselffrom__iadd__will cause variables using+=to silently becomeNone. - 3Use
total_orderingcautiously in performance-critical paths: While@functools.total_orderingsaves boilerplate, explicit implementations of comparison methods execute slightly faster by avoiding dynamic method wrapper lookups.
Multiple Choice Questions
1.
What should a binary operator dunder method like __add__(self, other) return when it encounters an unsupported type for other? A. raise TypeError B. raise NotImplementedError C. return NotImplemented D. return None
NotImplemented signals the Python runtime to attempt the operation using the right-hand operand's reflected method (__radd__). Raising an error immediately halts evaluation.2.
Given the expression result = 10 + custom_obj, where 10 is an int that does not know how to add custom_obj, which method is invoked on custom_obj? A. custom_obj.__add__(10) B. custom_obj.__radd__(10) C. custom_obj.__iadd__(10) D. custom_obj.__call__(10)
NotImplemented, Python attempts the reflected (reverse) operator method on the right operand, which is __radd__.3.
What critical value must an in-place operator like __iadd__(self, other) return when mutating a mutable object? A. None B. True C. self D. The previous value of self
a += b assigns the return value of __iadd__ back to a. If __iadd__ returns None (or omits a return statement), a is reassigned to None.4.
Which standard library decorator allows a class defining only __eq__ and __lt__ to automatically support all six rich comparison operators (<=, >, >=, !=)? A. @functools.lru_cache B. @functools.total_ordering C. @dataclasses.dataclass D. @operator.rich_comparison
@functools.total_ordering takes a class that defines __eq__ and any one of __lt__, __le__, __gt__, or __ge__, and programmatically supplies the remaining comparison methods.5.
Which operator corresponds to the special method __matmul__(self, other) introduced in Python 3.5? A. Regular matrix exponentiation (**) B. Matrix multiplication (@) C. Bitwise AND (&) D. Decorator application (@property)
@ binary operator was introduced in Python 3.5 (PEP 465) specifically for matrix multiplication and corresponds to the __matmul__ and __rmatmul__ dunder methods.Customizing Classes with Magic Methods
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| __str__, __repr__, __len__ | Customizing Classes with Magic Methods |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.