Los métodos especiales (también llamados "dunder methods" o "magic methods") son los que Python llama automáticamente cuando usas operadores, funciones built-in o ciertos patrones del lenguaje. Implementarlos en tus clases hace que se comporten como tipos nativos.
__str__ y __repr__
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
v = Vector(3, 4)
print(str(v)) # (3, 4) usa __str__
print(repr(v)) # Vector(3, 4) usa __repr__
print([v]) # [Vector(3, 4)] colecciones usan __repr__
__len__ y __bool__
class Inventario:
def __init__(self):
self._items = []
def agregar(self, item):
self._items.append(item)
def __len__(self):
return len(self._items)
def __bool__(self):
return len(self._items) > 0 # False si está vacío
inv = Inventario()
print(len(inv)) # 0
if not inv:
print("El inventario está vacío")
inv.agregar("laptop")
print(len(inv)) # 1
if inv:
print(f"Inventario con {len(inv)} items")
__eq__ y __hash__
Si defines __eq__, Python pone __hash__ a None automáticamente (el objeto deja de ser hashable). Define ambos si necesitas que los objetos sean comparables Y usables en sets/dicts:
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, otro):
if not isinstance(otro, Punto):
return NotImplemented
return self.x == otro.x and self.y == otro.y
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return f"Punto({self.x}, {self.y})"
p1 = Punto(1, 2)
p2 = Punto(1, 2)
p3 = Punto(3, 4)
print(p1 == p2) # True
print(p1 == p3) # False
# Hashable: se puede usar en set y como clave de dict
puntos = {p1, p2, p3}
print(puntos) # {Punto(1, 2), Punto(3, 4)} solo 2 elementos
Operadores aritméticos
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, otro):
return Vector(self.x + otro.x, self.y + otro.y)
def __sub__(self, otro):
return Vector(self.x - otro.x, self.y - otro.y)
def __mul__(self, escalar):
return Vector(self.x * escalar, self.y * escalar)
def __rmul__(self, escalar): # para 3 * v además de v * 3
return self.__mul__(escalar)
def __abs__(self):
return (self.x**2 + self.y**2) ** 0.5
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6)
print(abs(v2)) # 5.0
Context manager: __enter__ y __exit__
class Temporizador:
import time
def __enter__(self):
self.inicio = __import__('time').perf_counter()
return self
def __exit__(self, tipo, valor, tb):
self.duracion = __import__('time').perf_counter() - self.inicio
print(f"Tiempo: {self.duracion:.4f}s")
return False # no suprime excepciones
with Temporizador() as t:
resultado = sum(range(1_000_000))
print(t.duracion) # acceso al atributo tras el bloque
La lista completa de dunder methods es larga, pero los más usados son __repr__, __str__, __len__, __eq__ y los aritméticos. Implementarlos bien hace que tus clases se integren de forma natural con el resto del lenguaje.
