32 · Métodos dunder y sobrecarga de operadores
__add__, __len__, __getitem__, __call__ — el protocolo que hace que tus objetos funcionen con la sintaxis integrada de Python. Más dataclasses para el atajo moderno.
Los **métodos dunder** (doble subrayado en ambos lados) son el sistema de hooks de Python. `a + b` llama a `a.__add__(b)`. `len(x)` llama a `x.__len__()`. `for item in x` llama a `x.__iter__()`. `x[i]` llama a `x.__getitem__(i)`. `x(...)` llama a `x.__call__(...)`. Implementar los dunders correctos hace que tu clase se comporte como un tipo integrado — legible, componible y compatible con todo el ecosistema Python.
Sin esto:
Sin dunders, `Vector(1, 2) + Vector(3, 4)` lanza `TypeError` y `for muestra in mi_dataset:` falla. Los tensores de PyTorch soportan `+`, `*` y `@` (matmul) solo porque `torch.Tensor` implementa `__add__`, `__mul__` y `__matmul__`.
El modelo de datos de Python está construido sobre métodos dunder — métodos especiales con doble subrayado en ambos lados. Cuando escribes a + b, Python llama a a.__add__(b). Cuando escribes len(x), Python llama a x.__len__(). Este protocolo significa que tus clases personalizadas pueden participar en cualquier sintaxis de Python sin soporte especial del compilador — solo implementa los dunders correctos.
Dunders clave que conocer:
__init__— inicializa la instancia__repr__— string orientado al desarrollador (repr(obj), impreso en REPL)__str__— string orientado al usuario (str(obj), usado porprint)__eq__— igualdad (==); si se define sin__hash__, el objeto se vuelve no hashable__hash__— permite usar el objeto como clave de dict o elemento de set__len__—len(obj)__getitem__/__setitem__—obj[i]/obj[i] = v__iter__/__next__—for x in obje iteración manual__call__—obj(...)— hace una instancia llamable como función__add__,__mul__,__matmul__—+,*,@
dataclasses.dataclass (Python 3.7+) autogenera __init__, __repr__ y __eq__ a partir de anotaciones de campos — eliminando el boilerplate para contenedores de datos simples.
Python (in browser)
`__rmul__` maneja el caso en que Python intenta `3 * v1` y `int.__mul__(3, v1)` devuelve `NotImplemented` — Python entonces intenta `v1.__rmul__(3)`. Sin `__rmul__`, `3 * v1` lanza `TypeError`. Devolver `NotImplemented` (sin lanzar) es la señal correcta a Python para intentar la operación reflejada.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
El protocolo de bucle `for` de Python comprueba `__iter__` primero, luego recurre a llamar `__getitem__(0)`, `__getitem__(1)`, ... hasta `IndexError`. Implementar solo `__len__` y `__getitem__` es suficiente para hacer una clase iterable y compatible con el `DataLoader` de PyTorch, que espera exactamente estos dos métodos.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
`@dataclass` lee las anotaciones de tipo a nivel de clase y genera `__init__` (con valores por defecto), `__repr__` y `__eq__` en el momento de creación de la clase. No más escribir el mismo boilerplate `self.x = x` para cada campo. Añade `frozen=True` para hacer el dataclass inmutable (y hashable).
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
¿Qué método dunder debe implementar una clase para que sus instancias sean llamables como funciones — p.ej. `resultado = mi_obj(x)`?
La lección mml-3 cubre vectores y matrices — los objetos matemáticos sobre los que operan `+`, `*` y `@`. La sobrecarga de operadores (`__add__`, `__mul__`, `__matmul__`) es precisamente cómo NumPy y PyTorch hacen funcionar `a + b` y `A @ B` sobre arrays y tensores — la misma sintaxis aritmética que usas para escalares de Python se aplica ahora a datasets enteros.
- Los métodos dunder se enganchan a la sintaxis integrada de Python: `+` → `__add__`, `len()` → `__len__`, `obj[i]` → `__getitem__`, `for x in obj` → `__iter__`, `obj(...)` → `__call__`.
- Implementar `__len__` y `__getitem__` hace una clase iterable y compatible con el `DataLoader` de PyTorch — no se requiere `__iter__`.
- `@dataclass` autogenera `__init__`, `__repr__`, `__eq__` a partir de anotaciones de campos. Úsalo para objetos de configuración, contenedores de resultados y cualquier clase de datos simples para eliminar el boilerplate.
Los tensores de PyTorch sobrecargan `+`, `*`, `-`, `/` y `@` (multiplicación matricial) mediante `__add__`, `__mul__`, etc. — haciendo que `loss = predictions - targets` y `output = W @ x + b` sean legibles. `nn.Module.__call__` envuelve `forward` con hooks de gradiente, por lo que `model(x)` hace más que solo `model.forward(x)`. El `Dataset` de PyTorch requiere `__len__` y `__getitem__` — cualquier clase que los implemente se conecta a `DataLoader` con batching, barajado y carga multiproceso gratis. `@dataclass` es el estándar para objetos de configuración de experimentos en codebases de ML (Hydra, Lightning CLI, etc.).
Si lo quitas: Sin `__len__` y `__getitem__`, el `DataLoader` de PyTorch no puede procesar tu dataset en batches. Sin `__call__`, `model(x)` falla — tendrías que escribir `model.forward(x)` en todas partes, perdiéndote toda la infraestructura de hooks de gradiente que proporciona `nn.Module.__call__`.