Los decoradores permiten añadir comportamiento a funciones o clases sin modificar directamente su código principal. Se utilizan para registrar llamadas, medir tiempo, validar permisos, aplicar caché, repetir operaciones y construir frameworks. Comprenderlos ayuda a leer bibliotecas modernas de Python y a evitar duplicación.
Las funciones como objetos
En Python, una función puede asignarse a una variable, pasarse como argumento y devolverse desde otra función. Esta capacidad es la base de los decoradores. Si necesitas repasar el concepto, consulta la guía de funciones en Python.
def saludar():
return "Hola"
otra_referencia = saludar
print(otra_referencia())Primer decorador
Un decorador recibe una función y devuelve otra función que la envuelve:
def registrar(funcion):
def wrapper():
print("Antes de ejecutar")
resultado = funcion()
print("Después de ejecutar")
return resultado
return wrapper
@registrar
def tarea():
return "Tarea terminada"
print(tarea())La sintaxis @registrar equivale a tarea = registrar(tarea). El nombre original pasa a apuntar al wrapper.
Aceptar argumentos
Para decorar funciones con firmas diferentes, el wrapper suele recibir *args y **kwargs:
def auditoria(funcion):
def wrapper(*args, **kwargs):
print("Argumentos:", args, kwargs)
return funcion(*args, **kwargs)
return wrapperLa guía específica sobre *args y **kwargs explica cómo capturar y reenviar parámetros con seguridad.
Conservar metadatos con functools.wraps
Sin cuidados, el wrapper reemplaza el nombre, la documentación y otras propiedades de la función decorada. functools.wraps conserva esos metadatos:
from functools import wraps
def registrar(funcion):
@wraps(funcion)
def wrapper(*args, **kwargs):
print(f"Ejecutando {funcion.__name__}")
return funcion(*args, **kwargs)
return wrapperLa documentación de functools.wraps detalla este comportamiento. En decoradores reutilizables, su uso debe considerarse prácticamente obligatorio.
Decoradores con parámetros
Cuando el decorador necesita configuración, se añade otra función externa:
from functools import wraps
def repetir(veces):
def decorador(funcion):
@wraps(funcion)
def wrapper(*args, **kwargs):
resultado = None
for _ in range(veces):
resultado = funcion(*args, **kwargs)
return resultado
return wrapper
return decorador
@repetir(3)
def mensaje():
print("Hola")Ahora repetir(3) devuelve el decorador que recibirá la función.
Medir tiempo de ejecución
Un decorador puede medir duración, aunque para comparaciones rigurosas conviene usar herramientas adecuadas:
from functools import wraps
from time import perf_counter
def medir_tiempo(funcion):
@wraps(funcion)
def wrapper(*args, **kwargs):
inicio = perf_counter()
resultado = funcion(*args, **kwargs)
duracion = perf_counter() - inicio
print(f"Tiempo: {duracion:.6f} s")
return resultado
return wrapperPara localizar problemas reales de rendimiento, consulta la guía de cProfile y cuellos de botella, que ofrece una visión más completa que medir una sola llamada.
Decoradores para validación
También pueden comprobar condiciones antes de ejecutar:
from functools import wraps
def requiere_positivo(funcion):
@wraps(funcion)
def wrapper(valor):
if valor <= 0:
raise ValueError("El valor debe ser positivo")
return funcion(valor)
return wrapperEste enfoque sirve para reglas transversales, pero no conviene esconder toda la lógica del programa detrás de decoradores. Una función debe seguir siendo comprensible.
Orden de varios decoradores
Cuando una función tiene varios decoradores, se aplican de abajo hacia arriba. El decorador más cercano a la función actúa primero al construir la cadena. Al ejecutar, el flujo depende de cómo cada wrapper llame al siguiente. Por eso el orden puede cambiar autenticación, caché, registro o medición.
Decoradores de clase y decoradores en clases
Python incluye decoradores conocidos como @property, @classmethod y @staticmethod. También es posible crear clases decoradoras implementando __call__. Sin embargo, una función decoradora suele ser más sencilla para la mayoría de casos.
Errores frecuentes
Los fallos más comunes son olvidar devolver el resultado, no aceptar argumentos, perder metadatos, ejecutar la función al decorar en vez de al llamar y capturar excepciones de forma demasiado amplia. La guía sobre return en Python ayuda a entender por qué un wrapper debe devolver correctamente el valor.
El glosario oficial de Python define el concepto y muestra la equivalencia de la sintaxis con arroba.
Buenas prácticas
Usa nombres claros, aplica wraps, documenta los efectos y evita decoradores que alteren de manera sorprendente los datos. Prefiere funciones pequeñas y combina decoradores solo cuando el orden sea evidente. También puedes añadir type hints para mejorar la experiencia en editores, aunque tipar decoradores genéricos puede requerir herramientas avanzadas como ParamSpec.
Los decoradores son poderosos porque separan preocupaciones repetitivas del núcleo de una función. Utilizados con moderación, hacen el código más limpio; utilizados en exceso, pueden ocultar el flujo. La clave está en que el comportamiento añadido sea transversal, predecible y fácil de probar.
Aplicaciones reales
En proyectos web, los decoradores pueden comprobar autenticación, limitar acceso o registrar métricas. En scripts, pueden reintentar una operación temporalmente fallida, normalizar errores o aplicar caché. La biblioteca estándar incluye ejemplos conocidos: @lru_cache memoriza resultados y @singledispatch selecciona implementaciones según el tipo del primer argumento. Antes de crear un decorador propio, revisa si ya existe una solución estable.
Decoradores y excepciones
Un wrapper puede capturar excepciones, pero no debería ocultarlas sin una razón clara. Registrar el error y volver a lanzarlo conserva el comportamiento esperado. Devolver un valor predeterminado para cualquier excepción puede convertir fallos graves en resultados aparentemente válidos. Captura únicamente errores concretos y documenta cómo cambia el contrato de la función.
from functools import wraps
def registrar_errores(funcion):
@wraps(funcion)
def wrapper(*args, **kwargs):
try:
return funcion(*args, **kwargs)
except ValueError as error:
print(f"Valor inválido: {error}")
raise
return wrapperPruebas
Prueba tanto el efecto adicional como el resultado original. Verifica que el wrapper acepte la misma firma práctica, conserve el nombre y no ejecute la función antes de tiempo. Para decoradores con estado, crea instancias independientes o reinicia el estado entre pruebas. Un decorador difícil de probar suele estar haciendo demasiadas cosas.





