Dunder Methods
The Secret Behind Built-in Behaviour
When you write len([1, 2, 3]), Python calls list.__len__(). When you write "abc" + "def", Python calls str.__add__(). When you write 10 > 5, Python calls int.__gt__().
Every operator, every built-in function, every piece of syntax you've been using is backed by a dunder method — a method with double underscores on both sides. You've already seen two: __init__ and __str__. There are dozens more.
By implementing them in your own classes, you make your objects feel native to the language. They work with len(), with +, with <, with in, with indexing — exactly like built-in types.
__repr__ and __str__
You already know __str__ controls what print() shows. __repr__ is its complement — it provides an unambiguous, developer-facing representation, used in the REPL and for debugging.
The rule of thumb: __repr__ should ideally be a string that, if pasted into Python, recreates the object. __str__ is for human display. If you only define one, define __repr__ — Python falls back to it for __str__ when __str__ is absent.
__len__ and __bool__
Once __len__ is defined, len(playlist) works naturally. Once __bool__ is defined, if playlist: works — truthy when non-empty, falsy when empty. If __bool__ is not defined but __len__ is, Python uses len(obj) != 0 as the boolean value automatically.
Comparison Operators
Define these to make your objects comparable with ==, <, >, and so on:
| Dunder | Operator |
|---|---|
__eq__ | == |
__ne__ | != |
__lt__ | < |
__le__ | <= |
__gt__ | > |
__ge__ | >= |
Defining __lt__ and __eq__ is enough to make sorted() work on your objects. Python's sort uses __lt__ for comparisons.
A shortcut: from functools import total_ordering lets you define only __eq__ and one comparison method, and Python fills in the rest automatically.
Arithmetic Operators
v1 + v2 calls v1.__add__(v2). abs(v1) calls v1.__abs__(). Your Vector class now behaves like a native numeric type for these operations.
__getitem__, __setitem__, __contains__
These give your objects indexing (obj[key]), index assignment (obj[key] = value), and in support:
grid[0, 0] = 5 calls grid.__setitem__((0, 0), 5) — the tuple (0, 0) is passed as pos. 9 in grid calls grid.__contains__(9). Your class now supports natural Python syntax.
__call__ — Making Objects Callable
Adding __call__ makes an instance callable like a function:
double(10) calls double.__call__(10). The object behaves exactly like a function — but carries its own state. This is the foundation of callable objects and, in machine learning, of the layer objects in frameworks like PyTorch.
What You Have Learned
Dunder methods are Python's interface for making objects feel native to the language — not special cases, but full participants in its syntax.
The key ideas:
- Dunders power all operators and built-in functions behind the scenes
__repr__— developer representation;__str__— human display; define__repr__first__len__and__bool__enablelen()and truthiness checks- Comparison dunders (
__eq__,__lt__, etc.) enable operators andsorted() - Arithmetic dunders (
__add__,__mul__, etc.) enable operators on custom types __getitem__,__setitem__,__contains__enable indexing andin__call__makes an instance callable like a function
In the next lesson, you'll go deeper into comprehensions — dict and set comprehensions, nested comprehensions, and the powerful zip and enumerate tools.