Cuando usamos decoradores, envolvemos una función con otra.
Esto tiene un efecto colateral: la función decorada pierde información importante como su nombre original (__name__) y su documentación (__doc__).
Esto puede traer problemas en:
Veamos con un ejemplo el problema:
def mi_decorador(func): def envoltura(*args, **kwargs): """Función envoltorio sin docstring de la original""" print("Antes de la función...") resultado = func(*args, **kwargs) print("Después de la función...") return resultado return envoltura @mi_decorador def saludar(): """Esta función imprime un saludo amigable.""" print("¡Hola!") print(saludar.__name__) # envoltura print(saludar.__doc__) # Función envoltorio sin docstring de la original
Como podemos ver, el nombre y la docstring de saludar se perdieron y nos muestran los de la función envoltura.
Este decorador copia los metadatos de la función original a la función envoltura.
import functools def mi_decorador(func): @functools.wraps(func) def envoltura(*args, **kwargs): """Función envoltorio""" print("Antes de la función...") resultado = func(*args, **kwargs) print("Después de la función...") return resultado return envoltura @mi_decorador def saludar(): """Esta función imprime un saludo amigable.""" print("¡Hola!") print(saludar.__name__) # saludar print(saludar.__doc__) # Esta función imprime un saludo amigable.
@functools.wraps(func) es en realidad un decorador de decoradores.
Lo que hace internamente es:
Copiar atributos importantes (__name__, __doc__, __module__, __annotations__) desde la función original a la función envoltura.
Agregar un atributo especial __wrapped__ que apunta a la función original.
Esto asegura que la función decorada se comporte como la original en cuanto a metadatos.
Siempre que crees un decorador que envuelva una función, usa @functools.wraps.
Esto lo hace más profesional, robusto y compatible con herramientas externas.