Cuando usas un decorador, estás reemplazando una función con otra. En otras palabras, si tienes un decorador
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
entonces cuando dices
@logged
def f(x):
"""does some math"""
return x + x * x
es exactamente lo mismo que decir
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
y su función f
se reemplaza con la función with_logging
. Desafortunadamente, esto significa que si dices
print(f.__name__)
se imprimirá with_logging
porque ese es el nombre de su nueva función. De hecho, si observa la cadena de documentación f
, estará en blanco porque with_logging
no tiene cadena de documentación, por lo que la cadena de documentación que escribió ya no estará allí. Además, si observa el resultado de pydoc para esa función, no aparecerá como un argumento x
; en su lugar, aparecerá como tomando *args
y **kwargs
porque eso es lo que toma with_logging.
Si usar un decorador siempre significa perder esta información sobre una función, sería un problema grave. Por eso lo tenemos functools.wraps
. Esto toma una función utilizada en un decorador y agrega la funcionalidad de copiar sobre el nombre de la función, la cadena de documentos, la lista de argumentos, etc. Y como wraps
es en sí mismo un decorador, el siguiente código hace lo correcto:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'