Pregunta 1Cuando se ejecuta result = a + b, ¿qué método llama Python entre bastidores?
Métodos especiales — Enseñar a tu clase cómo se comportan + y print
Aprende los métodos especiales de Python (métodos dunder). Personaliza + con __add__, controla la salida de print con __str__ y redefine == con __eq__ — todo con diagramas.
La última vez ordenamos los métodos de instancia, de clase y estáticos. Ahora cubriremos un tipo distinto — los métodos especiales (también conocidos como métodos dunder).
¿Qué son los métodos especiales? — "Métodos dunder"
Métodos como __init__ y __str__ — nombres rodeados de dos guiones bajos a cada lado — se llaman métodos especiales. Como están entre double underscores (dobles guiones bajos), también se les conoce como métodos dunder.
Lo clave de los métodos especiales es que no los llamas directamente. Python los llama automáticamente cuando realizas ciertas operaciones. Escribe v1 + v2 y Python llama a v1.__add__(v2) por debajo. Escribe print(p) y se llama a p.__str__(). Escribe a == b y se ejecuta a.__eq__(b).
+, == y compañía.__add__ — Definir el operador +
Toma + como ejemplo evidente. Supón que tienes una clase Money para importes en yenes y quieres que Money(300) + Money(500) te dé Money(800). Intenta sumarlos sin más y Python lanzará un TypeError quejándose de que no sabe cómo sumar Money con Money.
La forma de enseñar a Python a sumarlos es el método __add__. Define def __add__(self, other): y devuelve una nueva instancia construida a partir de self (lado izquierdo) y other (lado derecho). Ahora + funciona en tu clase.
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other): # se llama al escribir +
return Money(self.amount + other.amount)
wallet = Money(300)
payment = Money(500)
total = wallet + payment # en realidad wallet.__add__(payment)
print(total.amount) # 800
+ y Python llama a self.__add__(other). self es el operando izquierdo, other el derecho. La nueva instancia que devuelvas se convierte en el resultado de +.__str__ y __repr__ — Dos tipos de representación de cadena
Los siguientes: los métodos especiales que se llaman cuando una instancia se convierte en cadena. Hay dos, con objetivos distintos.
- __str__ — llamado por print(p) y str(p). Una cadena legible para el usuario.
- __repr__ — llamado en el REPL o para depuración. Una cadena estilo código, detallada.
Sin sobrescrituras, print(user) muestra algo como <__main__.User object at 0x...> — bastante inútil. Define __str__ para una visualización limpia, y define también __repr__ para que la depuración te muestre el tipo y el contenido de un vistazo.
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self): # para print() / str()
return f"{self.name} ({self.age} años)"
def __repr__(self): # para depuración
return f"User(name={self.name!r}, age={self.age})"
u = User("Ana", 30)
print(u) # Ana (30 años) <- __str__
print(repr(u)) # User(name='Ana', age=30) <- __repr__
- Llamado por
print(u)ystr(u) - Objetivo: una cadena que lean los usuarios finales
- Ejemplo:
Ana (30 años)
- Llamado por
repr(u)y por el REPL interactivo - Aquí cae como respaldo cuando falta __str__ durante print
- Objetivo: una cadena de depuración con tipo y contenido
- Ejemplo:
User(name='Ana', age=30)
__repr__ — facilita mucho la depuración.__eq__ — Definir ==
Ahora la igualdad. Cuando escribes a == b, Python llama internamente a a.__eq__(b). Si tu clase no define __eq__, el comportamiento por defecto es "¿son el mismo objeto en memoria?" (una comparación de id).
Digamos que tienes una clase Coupon y quieres que dos cupones con el mismo código cuenten como el mismo cupón — incluso si los valores de descuento difieren. Esa es una igualdad "a nivel de negocio", y __eq__ es donde lo deletreas.
== es una comprobación de id — el mismo contenido pero instancias separadas siguen dando False. Con __eq__ puedes comparar por contenido (code).class Coupon:
def __init__(self, code, discount):
self.code = code
self.discount = discount
def __eq__(self, other):
return self.code == other.code # mismo code = mismo cupón
c1 = Coupon("SPRING10", 0.10)
c2 = Coupon("SPRING10", 0.20) # descuento distinto, mismo code
c3 = Coupon("SUMMER15", 0.15)
print(c1 == c2) # True (los codes coinciden)
print(c1 == c3) # False (los codes difieren)
¿Qué pasa sin __eq__?
Si no defines __eq__, c1 == c2 comprueba si son el mismo objeto en memoria (literalmente apuntan a la misma caja). Aunque cada atributo coincida perfectamente, dos instancias construidas por separado viven en direcciones de memoria distintas y el resultado es False. Siempre que quieras "igual por contenido", define tú mismo __eq__.
Otros métodos especiales comunes — __call__ / __len__ / __bool__
Más allá de los cuatro que hemos cubierto, varios otros métodos especiales aparecen a menudo. Tres que vale la pena conocer ya:
- __call__ — hace que una instancia sea invocable como una función (sintaxis obj(...))
- __len__ — define lo que devuelve len(obj)
- __bool__ — define cómo se evalúa la instancia como valor de verdad en if obj: o bool(obj)
Cada uno enseña una operación integrada — llamada de función, len(), la comprobación de veracidad de if — a tu clase personalizada. Un Logger que acumula entradas de log es un gran ejemplo para poner los tres en una sola clase.
class Logger:
def __init__(self, name):
self.name = name
self.log = []
def __call__(self, message): # logger("...") funciona
self.log.append(message)
return f"[{self.name}] {message}"
def __len__(self): # para len(logger)
return len(self.log)
def __bool__(self): # para if logger:
return len(self.log) > 0
app = Logger("app")
print(app("App iniciada")) # [app] App iniciada
print(app("Login correcto")) # [app] Login correcto
print(len(app)) # 2
if app:
print("Hay logs") # Hay logs
logger(...), len(logger), if logger: — se enseña a tu clase personalizada mediante tres dunders correspondientes.¿Y si no defines __bool__?
Cuando falta __bool__, Python cae en __len__. Una longitud de 0 se vuelve False; cualquier otra cosa se vuelve True. Si faltan ambos, las instancias son siempre verdaderas, sin importar su estado. Esa es la misma regla que hace que las listas y cadenas vacías se evalúen como False.
Hay muchos más — __hash__ (para usar como clave de set/dict), __lt__ / __gt__ (las comparaciones < / >), __getitem__ (la sintaxis obj[key]), __iter__ (iteración for x in obj:) y otros. No necesitas memorizarlos todos. Solo guarda este mapa mental: "si quieres enseñar una operación integrada a tu clase, probablemente haya un dunder correspondiente para ello." Luego búscalo cuando lo necesites.
| Método especial | Sintaxis | Cuándo se llama |
|---|---|---|
| __init__ | Money(300) | Al crear la instancia |
| __add__ | a + b | Operador + |
| __str__ | print(p) / str(p) | Cadena para el usuario |
| __repr__ | repr(p) / pantalla del REPL | Cadena para el desarrollador |
| __eq__ | a == b | Comparación de igualdad |
| __call__ | obj(...) | Llamada estilo función |
| __len__ | len(obj) | Longitud |
| __bool__ | if obj: / bool(obj) | Veracidad |
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Qué descripción de __str__ y __repr__ es más exacta?
Pregunta 3Si una clase no define __eq__, ¿qué compara a == b? (Ambas son instancias de la misma clase.)