Objects · Lesson 14 of 18 · 20 min
Protocols and dunders
The Python data model: containers, callables, and dataclasses.
- Implement sequence and callable protocols
- Know the dunders that make `len`, `[]`, and `in` work
- Reach for dataclasses for record types
Python runs on protocols
`len(x)` is `x.__len__()`. `x[i]` is `x.__getitem__(i)`. `x + y` is `x.__add__(y)` (or `y.__radd__(x)`). You do not inherit from a Sequence base class to be iterable — you implement `__iter__` (or `__getitem__` with 0-based indexes). This is duck typing with a well-documented list of special methods, collected in the data model.
Dunder methods should be called via the built-in (`len(x)` not `x.__len__()`), because the built-in can refuse nonsense (negative lengths) and handle C-level types that have no Python-visible method.
class Range:
def __init__(self, n):
self.n = n
def __len__(self):
return self.n
def __getitem__(self, i):
if i < 0:
i += self.n
if i < 0 or i >= self.n:
raise IndexError(i)
return i * i
def __repr__(self):
return f"Range({self.n})"
r = Range(4)
print(len(r), r[2], list(r))dataclasses write the boring dunders
`@dataclass` generates `__init__`, `__repr__`, and `__eq__` from annotated fields. Add `frozen=True` for immutability (and then you may set `order=True` or a safe `__hash__`). `field(default_factory=list)` is the correct empty-list default.
For a simple bag of data, a dataclass (or `NamedTuple`) beats a handwritten class. When behavior grows — invariants, many methods, polymorphism — a regular class is clearer.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class User:
name: str
tags: tuple[str, ...] = ()
@dataclass
class Basket:
items: list[str] = field(default_factory=list)
print(User("ada") == User("ada"))
b = Basket()
b.items.append("book")
print(b)Check yourself
Quiz
1.`len(x)` calls:
2.The correct empty list default on a dataclass field is:
3.list(obj) works when obj:
Practice
Exercise
Write a class `Pair` with `__init__(self, left, right)`, `__len__` returning 2, and `__getitem__` so that p[0] is left and p[1] is right. Raise IndexError for other indexes.
Tutor
Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.