Special Methods
Special methods — also called "dunder" methods because they're surrounded by double underscores — let your classes work with Python's built-in syntax: print(), ==, +, len(), in, indexing, for loops and with blocks. They're how built-in types do it, too.
Representation
class Money: def __init__(self, amount, currency="USD"): self.amount = amount self.currency = currency def __repr__(self): return f"Money({self.amount!r}, {self.currency!r})" def __str__(self): return f"{self.amount:,.2f} {self.currency}" def __format__(self, spec): return format(self.amount, spec or ",.2f") + " " + self.currency m = Money(1234.5) print(m) print(repr(m)) print(f"Total: {m:.0f}")
1,234.50 USD Money(1234.5, 'USD') Total: 1234 USD
Comparison and equality
from functools import total_ordering @total_ordering class Version: def __init__(self, text): self.parts = tuple(int(p) for p in text.split(".")) def __eq__(self, other): return self.parts == other.parts def __lt__(self, other): return self.parts < other.parts def __hash__(self): return hash(self.parts) def __repr__(self): return ".".join(map(str, self.parts)) versions = [Version("1.10.0"), Version("1.2.3"), Version("1.9")] print(sorted(versions)) print(Version("2.0") > Version("1.99"), Version("1.0") == Version("1.0")) print(len({Version("1.0"), Version("1.0")}))
[1.2.3, 1.9, 1.10.0] True True 1
Without __eq__, two objects are only equal if they're the same object. @total_ordering fills in <=, > and >= from __eq__ and __lt__. If you define __eq__, also define __hash__ so objects can go in sets and dictionaries.
Arithmetic operators
class Vector: def __init__(self, x, y): self.x, self.y = x, y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar) __rmul__ = __mul__ def __abs__(self): return (self.x ** 2 + self.y ** 2) ** 0.5 def __repr__(self): return f"Vector({self.x}, {self.y})" a = Vector(1, 2) b = Vector(3, 4) print(a + b) print(a * 3, 3 * a) print(abs(b))
Vector(4, 6) Vector(3, 6) Vector(3, 6) 5.0
__rmul__ handles the reversed form 3 * a, where the left operand is a number that doesn't know about vectors.
Containers: len, in, indexing, iteration
class Deck: ranks = "A23456789TJQK" def __init__(self): self.cards = [r + s for s in "♠♥" for r in self.ranks] def __len__(self): return len(self.cards) def __getitem__(self, index): return self.cards[index] def __contains__(self, card): return card in self.cards def __iter__(self): return iter(self.cards) deck = Deck() print(len(deck)) print(deck[0], deck[-1], deck[2:5]) print("Q♥" in deck, "Q♦" in deck) print(sum(1 for card in deck if card[0] in "JQK"))
26 A♠ K♥ ['3♠', '4♠', '5♠'] True False 6
With __getitem__ supporting slices, the deck behaves much like a list. Python even makes it iterable automatically, though an explicit __iter__ is clearer.
Truthiness and calling
class Inbox: def __init__(self, messages): self.messages = messages def __bool__(self): return bool(self.messages) class Multiplier: def __init__(self, factor): self.factor = factor def __call__(self, value): return value * self.factor print("New mail!" if Inbox(["hi"]) else "Empty", "|", "New mail!" if Inbox([]) else "Empty") triple = Multiplier(3) print(triple(14))
New mail! | Empty 42
Context managers: __enter__ and __exit__
Objects used in a with statement set something up on entry and are guaranteed to clean up on exit — even if an error occurs:
import time class Timer: def __enter__(self): self.start = time.perf_counter() print("timer started") return self def __exit__(self, exc_type, exc, tb): elapsed = time.perf_counter() - self.start print(f"timer stopped ({'fast' if elapsed < 1 else 'slow'})") return False with Timer() as t: total = sum(range(100_000)) print(total)
timer started timer stopped (fast) 4999950000
Files, locks and database connections all work this way. Returning False from __exit__ lets any exception continue propagating. The contextlib.contextmanager decorator offers a shorter way to write simple context managers using a generator.
Common special methods
| Method | Called by |
|---|---|
__init__ | Cls(...) |
__repr__, __str__, __format__ | repr(), str()/print(), f-strings |
__eq__, __lt__, __hash__ | ==, <, sets and dict keys |
__add__, __sub__, __mul__… | +, -, *… |
__len__, __getitem__, __contains__, __iter__ | len(), obj[i], in, for |
__bool__, __call__ | if obj:, obj(...) |
__enter__, __exit__ | with |