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.

A tiny sequence
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.

dataclass in a few lines
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. 1.`len(x)` calls:

  2. 2.The correct empty list default on a dataclass field is:

  3. 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.