Decoradores Personalizados en Clases-2

 

Decoradores Personalizados en Clases: Los Asistentes Mágicos 🎩✨

Imagina que tienes un equipo de desarrollo y quieres agregar superpoderes a tus métodos o clases sin modificar su código interno.

¿Qué es un decorador personalizado?

Es una función que modifica o extiende el comportamiento de otra función o clase. En clases, puedes decorar métodos o la clase completa.

Analogía del Equipo de Desarrollo 👨‍💻

python
# Nuestros "asistentes mágicos" (decoradores)
def supervisor(func):
    """Verifica que todo esté en orden antes de ejecutar"""
    def asistente(*args, **kwargs):
        print("🔍 Supervisor: Verificando permisos...")
        resultado = func(*args, **kwargs)
        print("✅ Supervisor: Tarea completada con éxito")
        return resultado
    return asistente

def logger(func):
    """Registra todas las actividades"""
    def asistente(*args, **kwargs):
        print(f"📝 Logger: Ejecutando {func.__name__}")
        resultado = func(*args, **kwargs)
        print(f"📝 Logger: {func.__name__} completado")
        return resultado
    return asistente

def medir_tiempo(func):
    """Mide cuánto tiempo toma una tarea"""
    import time
    def asistente(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fin = time.time()
        print(f"⏱️  Tiempo: {func.__name__} tomó {fin-inicio:.2f} segundos")
        return resultado
    return asistente

Decoradores en Métodos de Clase

python
class Desarrollador:
    def __init__(self, nombre):
        self.nombre = nombre
    
    @supervisor
    @logger
    def programar(self, proyecto):
        print(f"{self.nombre} está programando {proyecto}")
        return f"Código de {proyecto} listo"
    
    @medir_tiempo
    def probar_codigo(self, modulo):
        import time
        print(f"{self.nombre} probando {modulo}...")
        time.sleep(2)  # Simular trabajo pesado
        return f"{modulo} probado exitosamente"

# Uso
dev = Desarrollador("Ana")

print("=== PROGRAMAR ===")
resultado1 = dev.programar("Sistema de Pagos")
print(f"Resultado: {resultado1}")

print("\n=== PROBAR CÓDIGO ===")
resultado2 = dev.probar_codigo("Módulo de Usuarios")
print(f"Resultado: {resultado2}")

Salida:

text
=== PROGRAMAR ===
🔍 Supervisor: Verificando permisos...
📝 Logger: Ejecutando programar
Ana está programando Sistema de Pagos
📝 Logger: programar completado
✅ Supervisor: Tarea completada con éxito
Resultado: Código de Sistema de Pagos listo

=== PROBAR CÓDIGO ===
Ana probando Módulo de Usuarios...
⏱️  Tiempo: probar_codigo tomó 2.00 segundos
Resultado: Módulo de Usuarios probado exitosamente

Decoradores que Reciben Parámetros

python
def repetir(veces):
    """Decorador con parámetros - repite una tarea"""
    def decorador_real(func):
        def asistente(*args, **kwargs):
            resultados = []
            for i in range(veces):
                print(f"🔄 Repetición {i+1}/{veces}")
                resultado = func(*args, **kwargs)
                resultados.append(resultado)
            return resultados
        return asistente
    return decorador_real

def validar_entero(posicion):
    """Valida que un parámetro específico sea entero"""
    def decorador_real(func):
        def asistente(*args, **kwargs):
            # args[0] es self, args[posicion] es el parámetro a validar
            if posicion < len(args) and not isinstance(args[posicion], int):
                raise ValueError(f"El parámetro en posición {posicion} debe ser entero")
            return func(*args, **kwargs)
        return asistente
    return decorador_real

class Calculadora:
    @repetir(3)
    def saludar(self):
        return "¡Hola desde la calculadora!"
    
    @validar_entero(1)  # Valida que el primer parámetro (después de self) sea entero
    def multiplicar(self, a, b):
        return a * b

# Uso
calc = Calculadora()

print("=== REPETIR ===")
resultados = calc.saludar()
print(f"Resultados: {resultados}")

print("\n=== VALIDAR ===")
try:
    resultado = calc.multiplicar(5, 3)
    print(f"5 * 3 = {resultado}")
    
    calc.multiplicar(5, "no_es_numero")  # Esto fallará
except ValueError as e:
    print(f"❌ Error: {e}")

Decoradores de Clase Completa

python
def agregar_metodos_extra(cls):
    """Decora una clase completa agregando métodos nuevos"""
    class NuevaClase(cls):
        def metodo_nuevo(self):
            return f"Soy un método nuevo agregado a {cls.__name__}"
        
        def estadisticas(self):
            return f"Clase: {cls.__name__}, Métodos: {len(dir(self))}"
    
    return NuevaClase

def conectar_base_datos(cls):
    """Simula conexión a base de datos para la clase"""
    cls.base_datos_conectada = True
    cls.ultima_conexion = "2024-01-01 10:00:00"
    
    def conectar(self):
        return f"{cls.__name__} conectada a BD"
    
    cls.conectar = conectar
    return cls

@agregar_metodos_extra
@conectar_base_datos
class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre
    
    def presentarse(self):
        return f"Soy {self.nombre}"

# Uso
usuario = Usuario("Carlos")

print(usuario.presentarse())        # Método original
print(usuario.metodo_nuevo())       # Método agregado por decorador
print(usuario.estadisticas())       # Método agregado por decorador
print(usuario.conectar())           # Método agregado por decorador
print(f"BD conectada: {usuario.base_datos_conectada}")

Decoradores Útiles del Mundo Real

python
import functools

def cache_resultados(func):
    """Almacena resultados para no recalcular"""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        clave = str(args) + str(kwargs)
        if clave not in cache:
            cache[clave] = func(*args, **kwargs)
        else:
            print("♻️  Usando resultado en caché")
        return cache[clave]
    return wrapper

def requiere_autenticacion(func):
    """Verifica que el usuario esté autenticado"""
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        if not hasattr(self, 'autenticado') or not self.autenticado:
            return "❌ Error: Se requiere autenticación"
        return func(self, *args, **kwargs)
    return wrapper

class SistemaBanco:
    def __init__(self):
        self.autenticado = False
    
    def login(self, usuario, contraseña):
        if usuario == "admin" and contraseña == "1234":
            self.autenticado = True
            return "✅ Login exitoso"
        return "❌ Login fallido"
    
    @requiere_autenticacion
    def ver_saldo(self, cuenta):
        return f"💰 Saldo de cuenta {cuenta}: $1000"
    
    @requiere_autenticacion
    @cache_resultados
    def calcular_interes(self, monto, meses):
        print(f"Calculando interés para ${monto} por {meses} meses...")
        # Simular cálculo complejo
        import time
        time.sleep(1)
        return monto * (1 + 0.1) ** meses

# Uso
banco = SistemaBanco()

print(banco.ver_saldo("12345"))  # ❌ Error: Se requiere autenticación

print(banco.login("admin", "1234"))  # ✅ Login exitoso

print(banco.ver_saldo("12345"))  # ✅ Ahora funciona

print(banco.calcular_interes(1000, 12))  # Calcula por primera vez
print(banco.calcular_interes(1000, 12))  # ♻️ Usa caché

Creando Tu Propio Ecosistema de Decoradores

python
class DecoradoresProyecto:
    @staticmethod
    def validar_email(func):
        def wrapper(self, email, *args, **kwargs):
            if "@" not in email or "." not in email:
                raise ValueError("Email inválido")
            return func(self, email, *args, **kwargs)
        return wrapper
    
    @staticmethod
    def formato_titulo(func):
        def wrapper(self, texto, *args, **kwargs):
            texto_formateado = texto.title()
            return func(self, texto_formateado, *args, **kwargs)
        return wrapper

class GestorUsuarios:
    @DecoradoresProyecto.validar_email
    def crear_usuario(self, email, nombre):
        return f"Usuario {nombre} con email {email} creado"
    
    @DecoradoresProyecto.formato_titulo
    def crear_post(self, titulo):
        return f"Post publicado: {titulo}"

# Uso
gestor = GestorUsuarios()

print(gestor.crear_usuario("ana@example.com", "Ana García"))
print(gestor.crear_post("mi primer post en python"))

try:
    gestor.crear_usuario("email_invalido", "Juan")
except ValueError as e:
    print(f"Error: {e}")

Resumen Mágico 🎩

text
TUS DECORADORES PERSONALIZADOS = ASISTENTES MÁGICOS

@supervisor     → El vigilante que verifica permisos
@logger         → El escriba que registra todo
@medir_tiempo   → El relojero que cronometra
@repetir        → El repetidor que ejecuta múltiples veces
@validar        → El guardián que verifica datos
@cache          → El memorioso que recuerda resultados
@autenticar     → El portero que controla accesos

Ventajas de los decoradores personalizados:

  • ✅ Reutilización: Un decorador sirve para muchas clases

  • ✅ Mantenibilidad: Cambias en un solo lugar

  • ✅ Legibilidad: El código principal se mantiene limpio

  • ✅ Modularidad: Puedes combinar múltiples decoradores

¡Ahora tienes un equipo de asistentes mágicos para tus clases!

Comentarios

Entradas populares de este blog

¿Qué es un Closure?

Calculadora de edad

Funciones en Python: con y sin paréntesis