Decoradores para funciones sin parámetros
Los decoradores permiten envolver una función para extender su comportamiento sin alterar su definición original. Un caso típico es medir el tiempo de ejecución de una función.
import time
def evaluar_rendimiento(funcion):
def envoltura():
inicio = time.perf_counter()
resultado = funcion()
fin = time.perf_counter()
print(f"Tiempo de ejecución: {fin - inicio:.4f} segundos")
return resultado
return envoltura
@evaluar_rendimiento
def proceso_lento():
time.sleep(1)
return "Proceso completado"
print(proceso_lento())
Este decorador captura la función original mediante closure y devuelve una nueva función que mantiene la interfaz original pero añade el cálculo de tiempo. La sintaxis con @ permite una aplicación limpia.
Decoradores con argumentos
Cuando se necesita pasar parámetros al propio decorador, se emplea una capa adicional de funciones.
USUARIOS_REGISTRADOS = {"ana": "contraseña123", "pedro": "segura456"}
def verificar_autenticacion(requiere_admin=False):
def decorador(func):
def envoltura(usuario, *args, **kwargs):
if usuario not in USUARIOS_REGISTRADOS:
return "Acceso denegado: usuario no reconocido"
if requiere_admin and usuario != "ana":
return "Se requieren privilegios de administrador"
return func(usuario, *args, **kwargs)
return envoltura
return decorador
@verificar_autenticacion(requiere_admin=False)
def mostrar_perfil(nombre_usuario):
return f"Perfil del usuario: {nombre_usuario}"
@verificar_autenticacion(requiere_admin=True)
def modificar_sistema(nombre_usuario, accion):
return f"El usuario {nombre_usuario} ejecutó: {accion}"
print(mostrar_perfil("pedro"))
print(modificar_sistema("pedro", "reinicio"))
La esrtuctura envuelve el decorador original en una función fábrica que acepta los parámetros de configuración y devuelve el decorador real.
Aplicación múltiple de decoradores
Los decoradores se aplican de abajo hacia arriba, pero ejecutan de arriba hacia abajo. Esto permite crear capas de procesamiento.
def registrar_acceso(func):
def envoltura(usuario, *args, **kwargs):
print(f"[LOG] Acceso de {usuario}")
return func(usuario, *args, **kwargs)
return envoltura
def validar_permisos(nivel_requerido):
def decorador(func):
def envoltura(usuario, *args, **kwargs):
niveles = {"visitante": 1, "editor": 2, "administrador": 3}
if niveles.get(usuario, 0) < nivel_requerido:
return "Permisos insuficientes"
return func(usuario, *args, **kwargs)
return envoltura
return decorador
@registrar_acceso
@validar_permisos(nivel_requerido=2)
def editar_contenido(usuario, contenido):
return f"Contenido modificado por {usuario}: {contenido}"
print(editar_contenido("editor", "Nuevo texto"))
print(editar_contenido("visitante", "Intento de edición"))
La composición de decoradores crea un pipeline donde cada capa puede interceptar y condicionar la ejecución de la función subyacente.
Decoradores con parámetros de fábrica
Se pueden crear sistemas de decoradores configurables mediante funciones fábrica.
def crear_decorador(config):
def decorador(func):
def envoltura(*args, **kwargs):
if config.get("registrar"):
print(f"Invocando: {func.__name__}")
if config.get("max_intentos"):
for intento in range(config["max_intentos"]):
try:
return func(*args, **kwargs)
except Exception as e:
if intento == config["max_intentos"] - 1:
raise e
return func(*args, **kwargs)
return envoltura
return decorador
@crear_decorador({"registrar": True, "max_intentos": 3})
def operacion_arriesgada(valor):
import random
if random.random() < 0.7:
raise ValueError("Error aleatorio")
return f"Operación exitosa con valor: {valor}"
print(operacion_arriesgada(42))
Este patrón encapsula la lógica de configuración dentro del decorador, permitiendo un comportamiento altamente personalizable sin multiplicar funciones decoradoras.