Métodos especiales en clases de Python
Los métodos que comienzan con dos guiones bajos, como __init__, __str__, __doc__, __new__, se denominan "métodos mágicos" en Python. Estos métodos se ejecutan automáticamente ante ciertos eventos de la clase o del objeto. Para personalizar clases con funcionalidades específicas, es necesario sobrescribir estos métodos.
Python reserva todos los métodos de clase que comienzan con __ (dos guiones bajos) como métodos mágicos. Por lo tanto, al definir métodos de clase, se recomienda no usar __ como prefijo, excepto para los métodos mágicos predefinidos.
Clasificación de métodos mágicos
Métodos fundamentales
__new__(cls[, ...]): Primer método invocado al instanciar un objeto. Su primer parámetro es la clase misma, y decide si se llamará a __init__. Se usa para heredar tipos inmutables como tuplas o cadenas.
__init__(self[, ...]): Inicializador que se ejecuta al crear una instancia.
__del__(self): Destructor que se invoca al destruir una instancia.
__call__(self[, args...]): Permite que una instancia se comporte como una función: x(a, b) invoca x.__call__(a, b).
__len__(self): Define el comportamiento al usar len().
__repr__(self): Define el comportamiento al usar repr() o al inspeccionar el objeto.
__str__(self): Define el comportamiento al usar str() o al imprimir el objeto.
__bool__(self): Define el comportamiento al usar bool(), debe retornar True o False.
Métodos de atributos
__getattr__(self, name): Invocado al acceder a un atributo inexistente.
__getattribute__(self, name): Invocado al acceder a cualquier atributo de la clase.
__setattr__(self, name, value): Invocado al asignar un atributo.
__delattr__(self, name): Invocado al eliminar un atributo.
__get__(self, instance, owner): Define el comportamiento al obtener el valor de un descriptor.
__set__(self, instance, value): Define el comportamiento al modificar el valor de un descriptor.
__delete__(self, instance): Define el comportamiento al eliminar un descriptor.
Operadores de comparación
__lt__(self, other): Define el comportamiento del operador <.
__eq__(self, other): Define el comportamiento del operador ==.
__gt__(self, other): Define el comportamiento del operador >.
Operadores aritméticos
__add__(self, other): Define el comportamiento de la suma +.
__sub__(self, other): Define el comportamiento de la resta -.
__mul__(self, other): Define el comportamiento de la multiplicación *.
__truediv__(self, other): Define el comportamiento de la división real /.
__pow__(self, other[, modulo]): Define el comportamiento de la potencia **.
Operaciones inversas
__radd__(self, other): Invocado cuando el operando izquierdo no soporta la operación correspondiente.
Asignación aumentada
__iadd__(self, other): Define el comportamiento de +=.
__isub__(self, other): Define el comportamiento de -=.
Operadores unarios
__pos__(self): Define el comportamiento del operador unario +.
__neg__(self): Define el comportamiento del operader unario -.
__abs__(self): Define el comportamiento al usar abs().
Conversión de tipos
__int__(self): Define el comportamiento al usar int().
__float__(self): Define el comportamiento al usar float().
__index__(self): Convierte a entero para usar en operaciones de corte.
Contexto con sentencia with
__enter__(self): Define la inicialización al usar with.
__exit__(self, exc_type, exc_value, traceback): Define la finalización del bloque with.
Tipos de contanedor
__getitem__(self, key): Define la obtención de elementos con self[key].
__setitem__(self, key, value): Define la asignación con self[key] = value.
__delitem__(self, key): Define la eliminación con del self[key].
__iter__(self): Define la iteración sobre los elementos.
__contains__(self, item): Define el comportamiento del operador in.
Ejemplos prácticos
Método __repr__
class Ejemplo:
def __repr__(self):
return 'Representación personalizada'
obj = Ejemplo()
print(obj) # Salida: Representación personalizada
Método __bool__
class Bandera:
def __bool__(self):
return False
bandera = Bandera()
print(bool(bandera)) # Salida: False
Métodos de atributos
class ManejoAtributos:
def __getattr__(self, nombre):
print(f'Atributo {nombre} no encontrado')
def __setattr__(self, nombre, valor):
print(f'Setting {nombre} = {valor}')
super().__setattr__(nombre, valor)
def __delattr__(self, nombre):
print(f'Deleting {nombre}')
super().__delattr__(nombre)
obj = ManejoAtributos()
obj.nuevo = 42 # Salida: Setting nuevo = 42
print(obj.nuevo) # Salida: 42
del obj.nuevo # Salida: Deleting nuevo
Descriptor personalizado
class Descriptor:
def __init__(self, valor_inicial=0):
self.valor = valor_inicial
def __get__(self, instancia, propietario):
return self.valor
def __set__(self, instancia, nuevo_valor):
self.valor = nuevo_valor
class MiClase:
atributo = Descriptor()
inst = MiClase()
inst.atributo = 100
print(inst.atributo) # Salida: 100
Sobrecarga de operadores
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 __repr__(self):
return f'Vector({self.x}, {self.y})'
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Salida: Vector(4, 6)
Clase contenedora
class ListaLimitada:
def __init__(self, max_elementos):
self.elementos = []
self.max = max_elementos
def __getitem__(self, indice):
return self.elementos[indice]
def __setitem__(self, indice, valor):
if indice >= self.max:
raise IndexError('Límite excedido')
self.elementos.insert(indice, valor)
def __len__(self):
return len(self.elementos)
lista = ListaLimitada(5)
lista[0] = 'primero'
print(lista[0]) # Salida: primero
print(len(lista)) # Salida: 1