Métodos Dunder Esenciales en Python | Capítulo 30

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
Python

Aquí, __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')
Python

Ejecuta 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
Python

Aquí, __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
Python

Sin __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
Python

Aquí, __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 en print), 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.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio