En Python, un decorador de función de orden superior es una función que toma otra función (o clase) y devuelve una nueva función (o una clase modificada). Los decoradores se utilizan a menudo para implementar el patrón de "envoltura" (wrapping), que permite agregar lógica adicional (por ejemplo, registro, caché, verificación de permisos, etc.) a funciones existentes sin modificar su código fuente.
Para preservar el nombre, la documentación y otros metadatos de la función original, se recomienda utilizar la función functools.wraps:
import functools def log_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'Calling {func.__name__}') return func(*args, **kwargs) return wrapper @log_decorator def add(a, b): """Suma dos números""" return a + b print(add(2, 3)) # Salida: Calling add 5 print(add.__name__) # Salida: add print(add.__doc__) # Salida: Suma dos números
Punto clave: sin functools.wraps, la función envuelta perderá su nombre, documentación y otros metadatos del original, lo que afecta negativamente a la depuración y la autodocumentación.
Si decoras una función sin usar functools.wraps, ¿qué pasará con los atributos name y doc de la función?
Respuesta: Serán heredados de la función interna envoltora (normalmente 'wrapper'), y perderás los metadatos originales.
def simple_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @simple_decorator def f(): """Esta es la cadena de documentación""" pass print(f.__name__) # Salida: 'wrapper' (NO 'f') print(f.__doc__) # Salida: None (no 'Esta es la cadena de documentación')
Historia
En el proyecto se implementó un sistema complejo de decoradores para registrar los endpoints de la API, pero no se aplicó functools.wraps. Como resultado, la autogeneración de documentación (Swagger/OpenAPI) y las herramientas de introspección mostraban los nombres de todos los endpoints como 'wrapper', y la documentación desapareció. Esto dificultó seriamente el mantenimiento, las pruebas y el soporte.
Historia
Al escribir pruebas unitarias con pytest, falló la auto-descubrimiento de pruebas: las funciones de prueba decoradas con sus decoradores sin wraps no se detectaron porque su name era incorrecto. La razón es que pytest busca funciones por nombre.
Historia
Al rastrear la pila de excepciones (traceback), la pila de llamadas siempre apuntaba a 'wrapper', y era imposible entender qué función había causado el error, dado que los metadatos raíz se perdieron por la falta de functools.wraps en los decoradores personalizados.