Dunder / Magic Methods
Double-underscore methods let your classes participate in Python's syntax - operators, context managers, iteration, and string formatting. Implement them deliberately; defaults are not always correct for value objects.
Search across all documentation pages
Double-underscore methods let your classes participate in Python's syntax - operators, context managers, iteration, and string formatting. Implement them deliberately; defaults are not always correct for value objects.
class Vector:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)When to reach for this:
+, ==, or orderinglen, getitemwith cleanup__repr__ on domain objectsclass Shelf:
def __init__(self, items: list[str]) -> None:
self._items = list(items)
def __len__(self) -> int:
return len(self._items)
def __getitem__(self, index: int) -> str:
return self._items[index]
def __contains__(self, item: object) -> bool:
return item in self._items
def __repr__(self) -> str:
return f"Shelf({self._items!r})"
class Money:
__slots__ = ("amount", "currency")
def __init__(self, amount: int, currency: str) -> None:
self.amount = amount
self.currency = currency
def __eq__(self, other: object) -> bool:
if not isinstance(other, Money):
return NotImplemented
return self.amount == other.amount and self.currency == other.currency
def __hash__(self) -> int:
return hash((self.amount, self.currency))
if __name__ == "__main__":
s = Shelf(["a", "b"])
print(len(s), "a" in s, s[0])
cache = {Money(100, "USD"): "ok"}
print(cache[Money(100, "USD")])What this demonstrates:
__len__, __getitem__, __contains____eq__ returning NotImplemented for unknown types__hash__ enables use as dict key__repr__ shows developer-oriented reconstruction hint__init__ vs __new__ - __new__ creates instance; __init__ initializes. Rarely override __new__.__lt__ etc. enable sorted and @total_ordering.__iter__ + __next__ or generator methods.| Method | Triggered by |
|---|---|
__repr__ | repr(), interactive display |
__str__ | str(), print() |
__eq__ | == |
__hash__ | hash(), dict/set key |
__enter__/__exit__ | with statement |
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, major: int, minor: int) -> None:
self.major = major
self.minor = minor
def __eq__(self, other: object) -> bool:
...
def __lt__(self, other: "Version") -> bool:
return (self.major, self.minor) < (other.major, other.minor)__hash__ or use frozen dataclass.NotImplemented, not False, for ==.| Alternative | Use When | Don't Use When |
|---|---|---|
@dataclass | Boilerplate repr/eq | Custom operator semantics |
NamedTuple | Immutable simple records | Need mutability |
attrs library | Rich validation ecosystem | Stdlib-only constraint |
| functions | Single operation on data | Operators improve readability |
__repr__ for developers and logs - unambiguous. __str__ for end-user messages. Fallback chain: str → repr.
When instances should be dict keys or set members and equality is value-based with immutable fields.
Return from rich comparison when other type unknown - Python tries reversed operation on other operand.
Implement both for iterator objects. Iterable classes only need __iter__ yielding iterator.
Yes - eq, order, frozen, repr flags control generated methods.
__bool__ defines truthiness. __len__ returning 0 makes falsy unless __bool__ overrides.
Slots restrict attributes; some dunder attrs must be in __slots__ list if used.
Great for math vectors, money, paths. Poor for unrelated metaphors (+ on unrelated types).
Makes instances callable - functor pattern, decorator objects, stateful callbacks.
Subclass MutableMapping etc. to inherit required method checklist and doc guidance.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026