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.