107 - Preservando metadatos mediante el decorador functools.wraps

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:

  • Depuración: los errores mostrarán el nombre de la función envoltura en lugar del de la función real.
  • Documentación: herramientas como help() o pydoc mostrarán la docstring incorrecta.
  • Metaprogramación: si inspeccionamos funciones (con inspect), obtendremos datos erróneos.

Veamos con un ejemplo el problema:

Programa: ejercicio362.py

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.

La solución: functools.wraps

Este decorador copia los metadatos de la función original a la función envoltura.

Programa: ejercicio363.py


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.

Buenas prácticas

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.