Dunder Methods: Making Your Objects Feel Like Python
Dunder methods (double underscore methods, also called "magic methods" or "special methods") let your objects integrate with Python's built-in syntax and functions. When Python sees len(obj), it calls obj.__len__(). When it sees a + b, it calls a.__add__(b). By implementing these methods, your classes become first-class citizens in the Python ecosystem.
str vs repr
These are the two most important dunder methods for any class:
__repr__(self): Developer representation. Should ideally return a string that could recreate the object. Used in the REPL, debugging, and logging. Always implement this one.__str__(self): User-friendly representation. Used byprint()andstr(). Falls back to__repr__if not defined.
eq and Comparison Methods
By default, == compares object identity (same as is). Define __eq__ to compare by value:
Return NotImplemented (not NotImplementedError) when the comparison doesn't make sense โ Python will then try the reversed operation on the other operand.
Ordering: lt, le, gt, ge
Define all six comparison methods, or use @functools.total_ordering with just __eq__ and one of __lt__/__le__/__gt__/__ge__:
len and bool
__len__ makes len(obj) work. __bool__ controls truthiness. If you define __len__ but not __bool__, Python uses len(obj) != 0 for truth testing.
contains
__contains__ makes the in operator work:
iter and getitem
These make your object iterable. Implement __iter__ to return an iterator, or __getitem__ for sequence-style access (Python will create an iterator from it automatically):
add, mul, and Arithmetic
hash
If you define __eq__, Python sets __hash__ = None, making your object unhashable. Define __hash__ to make it usable in sets and dicts:
Only make objects hashable if they are immutable (or if equal objects will always hash the same). Mutable objects should generally not be hashable.
Quick Reference
| Dunder Method | Triggered By |
|---|---|
__repr__ | repr(x), REPL display |
__str__ | str(x), print(x) |
__eq__ | x == y |
__lt__, __le__, etc. | x < y, x <= y, etc. |
__len__ | len(x) |
__bool__ | bool(x), if x |
__contains__ | item in x |
__iter__ | for item in x, list(x) |
__getitem__ | x[i] |
__add__ | x + y |
__mul__ | x * y |
__hash__ | hash(x), sets, dict keys |
Knowledge Check
Why should `__eq__` return `NotImplemented` (not raise an error) when it can't compare with the other object?
If you define `__eq__` on a class but not `__hash__`, what happens?
What does `@functools.total_ordering` do when used with `__eq__` and `__lt__`?