Un decorador es una función que recibe una función y devuelve otra función extendida. Te agrega comportamiento —logging, caché, validación— sin modificar el código original.
La idea en crudo
def registro(func):
def wrapper():
print("→ entrando a", func.__name__)
resultado = func()
print("← saliendo de", func.__name__)
return resultado
return wrapper
def trabajar():
print("trabajando...")
trabajar = registro(trabajar) # "envolver" a mano
trabajar()
# → entrando a trabajar
# trabajando...
# ← saliendo de trabajar
La sintaxis @: azúcar del lenguaje
@registro
def trabajar():
print("trabajando...")
@registro encima de def es exactamente equivalente a trabajar = registro(trabajar). Solo azúcar — pero azúcar que se lee genial.
Decoradores con argumentos (el patrón completo)
La función original puede recibir parámetros; el wrapper los reenvía con *args, **kwargs:
def registro(func):
def wrapper(*args, **kwargs):
print(f"→ {func.__name__}({args})")
resultado = func(*args, **kwargs)
print(f"← {func.__name__} devolvió {resultado}")
return resultado
return wrapper
@registro
def sumar(a, b):
return a + b
sumar(2, 3)
# → sumar((2, 3))
# ← sumar devolvió 5
Este es el 90% de los decoradores que verás: wrapper(*args, **kwargs) reenvía todo.
functools.wraps: el detalle profesional
El wrapper "se queda" con la identidad de la función original:
import functools
def registro(func):
@functools.wraps(func) # ← preserva nombre y docstring
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@registro
def sumar(a, b):
"""Suma dos números."""
return a + b
print(sumar.__name__) # sumar (sin wraps sería "wrapper")
print(sumar.__doc__) # Suma dos números.
Sin @functools.wraps, el debugging y la introspección se rompen. Hábito: siempre inclúyelo.
Decoradores que reciben parámetros
Un nivel más de envoltura — una función que fabrica el decorador:
def repetir(veces):
def decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
resultado = None
for _ in range(veces):
resultado = func(*args, **kwargs)
return resultado
return wrapper
return decorador
@repetir(veces=3)
def saludar():
print("¡hola!")
saludar() # ¡hola! x3
Casos de uso reales
@app.route("/usuarios") # Flask/FastAPI: registrar rutas
def listar_usuarios(): ...
@login_requerido # autenticación
def panel(): ...
@cache # functools: cachear resultados
def calculo_costoso(): ...
Los frameworks de Python están construidos sobre decoradores. Dominarlos es leer mejor el ecosistema entero.
Resumen
- Decorador = función que envuelve otra para extenderla
@decorador≡func = decorador(func)wrapper(*args, **kwargs)reenvía los argumentos; devuelve el resultado@functools.wraps(func)preserva la identidad de la original- Para decoradores con opciones: una función fabrica el decorador