Volver a Python Intermedio
Decoradores y Clausuras
Los decoradores son un patrón poderoso para modificar o extender el comportamiento de funciones sin cambiar su código fuente.
Clausuras
Una clausura es una función que recuerda las variables de su ámbito externo:
def crear_multiplicador(n):
def multiplicar(x):
return x * n # n está "capturada"
return multiplicar
doble = crear_multiplicador(2)
triple = crear_multiplicador(3)
print(doble(5)) # 10
print(triple(5)) # 15
Decorador Básico
def registrar_llamadas(func):
def wrapper(*args, **kwargs):
print(f"Llamando a {func.__name__}...")
resultado = func(*args, **kwargs)
print(f"{func.__name__} retornó: {resultado}")
return resultado
return wrapper
@registrar_llamadas
def sumar(a, b):
return a + b
sumar(3, 4)
# Llamando a sumar...
# sumar retornó: 7
Preservar Metadatos de la Función
import functools
def registrar_llamadas(func):
@functools.wraps(func) # Preserva __name__, __doc__, etc.
def wrapper(*args, **kwargs):
print(f"Llamando a {func.__name__}...")
return func(*args, **kwargs)
return wrapper
Decorador con Argumentos
def reintentar(veces=3):
def decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for intento in range(veces):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Intento {intento+1} falló: {e}")
raise RuntimeError(f"Falló después de {veces} intentos")
return wrapper
return decorador
@reintentar(veces=3)
def llamada_inestable():
import random
if random.random() < 0.7:
raise ConnectionError("Error de red")
return "¡Éxito!"
Decoradores Basados en Clases
class Temporizador:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.llamadas = 0
def __call__(self, *args, **kwargs):
import time
self.llamadas += 1
inicio = time.perf_counter()
resultado = self.func(*args, **kwargs)
fin = time.perf_counter()
print(f"{self.func.__name__} tardó {fin-inicio:.4f}s (llamada #{self.llamadas})")
return resultado
@Temporizador
def funcion_lenta():
import time
time.sleep(0.1)
funcion_lenta()
funcion_lenta()
print(funcion_lenta.llamadas) # 2
Apilamiento de Decoradores
@registrar_llamadas
@reintentar(veces=2)
def obtener_usuario(user_id):
# Los decoradores se aplican de abajo hacia arriba:
# reintentar envuelve la función primero, luego registrar_llamadas
pass
Idea clave: Los decoradores son solo azúcar sintáctica para
func = decorador(func). Entender esto hace que las pilas de decoradores sean fáciles de razonar.