En Python, los métodos especiales —conocidos como dunder methods por sus dobles guiones bajos— son herramientas poderosas que permiten a tus clases personalizadas comportarse como tipos integrados. Imagina que estás diseñando un objeto que actúa como una lista o una cadena: estos métodos son el puente para lograrlo. En este capítulo, nos enfocamos en cinco esenciales: __str__, __repr__, __len__, __eq__ y __getitem__. Cada uno transforma cómo interactúas con tus objetos, desde representarlos en texto hasta compararlos o acceder a sus elementos. Exploraremos cada uno con profundidad, paso a paso, asegurándonos de que no solo los entiendas, sino que los domines para aplicarlos con confianza en tus proyectos.
Representando Objetos con str y repr
Comencemos por lo básico: cómo Python muestra tus objetos. Los métodos __str__ y __repr__ controlan esto, pero con matices importantes. Piensa en __str__ como la versión amigable para humanos, como una descripción casual de un amigo. En cambio, __repr__ es la versión técnica, precisa para depuración, como un informe detallado.
Primero, __str__ se invoca cuando usas print() o str() en un objeto. Si no lo defines, Python recurre a una representación predeterminada poco útil, como <__main__.MiClase object at 0x...>. Definirlo te permite personalizar esa salida.
Veamos un ejemplo simple. Crea un archivo llamado representacion.py y ejecútalo con python representacion.py.
class Libro:
def __init__(self, titulo, autor):
self.titulo = titulo
self.autor = autor
# Método __str__ para una representación legible
def __str__(self):
return f"{self.titulo} por {self.autor}" # Formato amigable para impresión
mi_libro = Libro("1984", "George Orwell")
print(mi_libro) # Salida: 1984 por George Orwell
PythonAquí, __str__ hace que print(mi_libro) muestre algo significativo. Sin él, obtendrías esa cadena genérica. Repito: este método es para salidas legibles, no para recrear el objeto.
Ahora, pasemos a __repr__, que se llama con repr() o en el intérprete interactivo. Su propósito es ser inequívoco, ideal para depuración. Una buena práctica es hacer que __repr__ devuelva una cadena que, si se evalúa con eval(), recree el objeto idéntico.
Ampliemos el ejemplo anterior en el mismo archivo:
class Libro:
def __init__(self, titulo, autor):
self.titulo = titulo
self.autor = autor
def __str__(self):
return f"{self.titulo} por {self.autor}"
# Método __repr__ para representación precisa y reproducible
def __repr__(self):
return f"Libro('{self.titulo}', '{self.autor}')" # Formato que permite recrear el objeto
mi_libro = Libro("1984", "George Orwell")
print(repr(mi_libro)) # Salida: Libro('1984', 'George Orwell')
PythonEjecuta esto y nota la diferencia. Si copias la salida de __repr__ y la usas en eval(Libro('1984', 'George Orwell')), obtendrías un objeto equivalente. Recuerda: si no defines __str__, Python usa __repr__ como fallback, pero es mejor definir ambos para claridad.
Midiendo Tamaño con len
El método __len__ permite que tus objetos respondan a la función integrada len(), como si fueran listas o cadenas. Es esencial para clases que representan colecciones. Imagina una mochila: __len__ te dice cuántos ítems lleva, sin necesidad de inspeccionarla manualmente.
Explica completamente: __len__ debe devolver un entero no negativo representando el “tamaño” lógico del objeto. No lo uses para algo arbitrario; mantén la intuición de longitud.
Ejemplo en un nuevo archivo longitud.py (ejecútalo con python longitud.py):
class Mochila:
def __init__(self):
self.items = [] # Lista interna para almacenar ítems
def agregar(self, item):
self.items.append(item) # Método para añadir ítems
# Método __len__ para devolver el número de ítems
def __len__(self):
return len(self.items) # Delega a la longitud de la lista interna
mi_mochila = Mochila()
mi_mochila.agregar("libro")
mi_mochila.agregar("botella")
print(len(mi_mochila)) # Salida: 2
PythonAquí, __len__ hace que len(mi_mochila) funcione naturalmente. Sin él, obtendrías un error: TypeError: object of type 'Mochila' has no len(). Domina esto: úsalo solo cuando tu clase modele una colección, asegurando consistencia con el ecosistema de Python.
Comparando Objetos con eq
La comparación es clave en programación, y __eq__ personaliza cómo tus objetos se evalúan como iguales con el operador ==. Por defecto, Python compara identidades (si son el mismo objeto en memoria), no valores. __eq__ cambia eso a igualdad semántica.
Paso a paso: __eq__ recibe self y other (el otro objeto). Debe devolver True si son iguales lógicamente, False иначе. Sé cauteloso con tipos: verifica si other es de la misma clase para evitar errores.
Analogía: Dos libros idénticos en contenido son “iguales”, aunque no sean el mismo ejemplar físico.
Ejemplo en comparacion.py (ejecuta python comparacion.py):
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
# Método __eq__ para comparar coordenadas
def __eq__(self, other):
if not isinstance(other, Punto): # Verifica tipo para seguridad
return False
return self.x == other.x and self.y == other.y # Comparación lógica
p1 = Punto(1, 2)
p2 = Punto(1, 2)
p3 = Punto(3, 4)
print(p1 == p2) # Salida: True
print(p1 == p3) # Salida: False
PythonSin __eq__, p1 == p2 sería False porque son objetos distintos. Repito lo importante: implementa __eq__ junto con __hash__ si usas objetos en sets o como claves de diccionarios, pero aquí nos limitamos a igualdad básica. Ahora lo dominas: usa esto para hacer comparaciones intuitivas.
Accediendo Elementos con getitem
Finalmente, __getitem__ habilita el acceso por índice o clave, como en listas (mi_lista[0]) o diccionarios (mi_dict['clave']). Hace que tu clase sea “subscriptable”, permitiendo sintaxis familiar.
Explicación profunda: Recibe self y key (el índice o clave). Debe devolver el valor correspondiente o lanzar IndexError/KeyError si es inválido. Para secuencias, key es un entero; para mappings, puede ser cualquier hashable.
Analogía: Como abrir un cajón específico en un archivador — __getitem__ es la llave maestra.
Ejemplo en acceso.py (ejecuta python acceso.py):
class Inventario:
def __init__(self):
self.items = ["espada", "escudo", "poción"] # Lista interna
# Método __getitem__ para acceso por índice
def __getitem__(self, index):
if 0 <= index < len(self.items): # Verifica límites
return self.items[index]
raise IndexError("Índice fuera de rango") # Error estándar
mi_inventario = Inventario()
print(mi_inventario[1]) # Salida: escudo
# print(mi_inventario[3]) # Lanza IndexError
PythonAquí, __getitem__ permite mi_inventario[1] como una lista real. Sin él, obtendrías TypeError: 'Inventario' object is not subscriptable. Has dominado esto: combínalo con __len__ para clases que emulen contenedores completos.
Resumen del capítulo
- Introducción a dunder methods: Son métodos especiales que personalizan el comportamiento de clases para integrarse con sintaxis de Python, enfocándonos en cinco esenciales sin metaprogramación.
- str y repr:
__str__ofrece representaciones legibles para humanos (usado enprint), mientras__repr__proporciona versiones precisas para depuración, ideales para recrear objetos. - len: Permite que
len()funcione en tus objetos, devolviendo un entero que representa el tamaño lógico, esencial para clases que modelan colecciones. - eq: Personaliza la igualdad con
==, comparando valores semánticos en lugar de identidades, con verificaciones de tipo para robustez. - getitem: Habilita el acceso por índice o clave, haciendo tus objetos subscriptables como listas o diccionarios, con manejo de errores adecuado.
- Dominio general: Cada método se explica con ejemplos ejecutables, analogías y código comentado, asegurando que apliques estos conceptos con confianza en proyectos reales de Python.