Métodos Dunder / Mágicos
Los métodos de doble guion bajo permiten que tus clases participen en la sintaxis de Python: operadores, gestores de contexto, iteración y formato de cadenas. Implementa estos métodos deliberadamente; los valores predeterminados no siempre son correctos para los objetos de valor.
Receta
class Vector:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)Cuándo usar esto:
- Tipos de valor con
+,==o ordenamiento natural. - Contenedores personalizados que implementan
len,getitem. - Tipos de recursos que necesitan limpieza con
with. __repr__útil para depuración en objetos de dominio.
Ejemplo de Trabajo
class Shelf:
def __init__(self, items: list[str]) -> None:
self._items = list(items)
def __len__(self) -> int:
return len(self._items)
def __getitem__(self, index: int) -> str:
return self._items[index]
def __contains__(self, item: object) -> bool:
return item in self._items
def __repr__(self) -> str:
return f"Shelf({self._items!r})"
class Money:
__slots__ = ("amount", "currency")
def __init__(self, amount: int, currency: str) -> None:
self.amount = amount
self.currency = currency
def __eq__(self, other: object) -> bool:
if not isinstance(other, Money):
return NotImplemented
return self.amount == other.amount and self.currency == other.currency
def __hash__(self) -> int:
return hash((self.amount, self.currency))
if __name__ == "__main__":
s = Shelf(["a", "b"])
print(len(s), "a" in s, s[0])
cache = {Money(100, "USD"): "ok"}
print(cache[Money(100, "USD")])Lo que esto demuestra:
- Protocolo de contenedor a través de
__len__,__getitem__,__contains__. - Igualdad de valor con
__eq__devolviendoNotImplementedpara tipos desconocidos. - La coincidencia de
__hash__permite su uso como clave de diccionario. __repr__muestra una pista de reconstrucción orientada al desarrollador.
Análisis Profundo
Cómo Funciona
- Modelo de datos: Python llama a los métodos dunder para operadores y funciones integradas.
__init__vs__new__:__new__crea la instancia;__init__la inicializa. Raramente se anula__new__.- Comparaciones enriquecidas:
__lt__etc. habilitansortedy@total_ordering. - Contrato de hash: Objetos iguales deben tener hashes iguales; el hash no debe cambiar mientras está en un diccionario/conjunto.
- Protocolo de iterador:
__iter__+__next__o métodos generadores.
Dunders Comunes
| Método | Desencadenado por |
|---|---|
__repr__ | repr(), visualización interactiva |
__str__ | str(), print() |
__eq__ | == |
__hash__ | hash(), clave de diccionario/conjunto |
__enter__/__exit__ | Sentencia with |
Notas de Python
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, major: int, minor: int) -> None:
self.major = major
self.minor = minor
def __eq__(self, other: object) -> bool:
...
def __lt__(self, other: "Version") -> bool:
return (self.major, self.minor) < (other.major, other.minor)Errores Comunes
__eq__sin__hash__: Hace que las instancias no sean "hasheables". Solución: Añadir un__hash__consistente o usar una dataclassfrozen.- Objetos mutables como claves con
__hash__: El hash no debe cambiar después de la inserción. Solución: Solo objetos de valor inmutables. __repr__devolviendo secretos de usuario: Fugas de tokens en los registros. Solución: Redactar campos sensibles.- Devolver
NotImplemented: Un tipo incorrecto debe devolverNotImplemented, noFalse, para==. - Sobrecargar todo: Comportamiento de operador sorprendente. Solución: Implementar solo operaciones naturales.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
@dataclass | Código repetitivo para repr/eq | Semántica de operador personalizada |
NamedTuple | Registros simples inmutables | Necesidad de mutabilidad |
Biblioteca attrs | Ecosistema de validación enriquecido | Restricción de solo biblioteca estándar |
| Funciones | Operación única sobre datos | Los operadores mejoran la legibilidad |
Preguntas Frecuentes
¿repr vs str?
__repr__ para desarrolladores y registros - sin ambigüedades. __str__ para mensajes del usuario final. Cadena de reserva: str → repr.
¿Cuándo implementar __hash__?
Cuando las instancias deben ser claves de diccionario o miembros de conjuntos y la igualdad se basa en el valor con campos inmutables.
¿Qué es NotImplemented?
Devolver desde una comparación enriquecida cuando el otro tipo es desconocido; Python intenta la operación inversa en el otro operando.
¿Necesito __iter__ y __next__?
Implementa ambos para objetos iteradores. Las clases iterables solo necesitan __iter__ que devuelva un iterador.
¿Puede dataclass generar dunders?
Sí - las banderas eq, order, frozen, repr controlan los métodos generados.
¿__bool__ vs __len__?
__bool__ define la veracidad. __len__ devolviendo 0 hace que sea falso a menos que __bool__ lo anule.
¿Interacción de __slots__?
Los slots restringen los atributos; algunos atributos dunder deben estar en la lista __slots__ si se usan.
¿Legibilidad de la sobrecarga de operadores?
Genial para vectores matemáticos, dinero, rutas. Pobre para metáforas no relacionadas (+ en tipos no relacionados).
¿Caso de uso de __call__?
Hace que las instancias sean llamables: patrón de functor, objetos decoradores, callbacks con estado.
¿Ayudantes de collections.abc?
Hereda de MutableMapping etc. para heredar la lista de métodos requeridos y la guía de documentación.
Relacionado
- dataclasses - dunders generados
- Propiedades y Descriptores - acceso a atributos
- Inmutabilidad y Hashabilidad - reglas de hash
- Gestores de Contexto - enter/exit
Versiones de pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.