Quando você usa um decorador, substitui uma função por outra. Em outras palavras, se você tem um decorador
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
então quando você diz
@logged
def f(x):
"""does some math"""
return x + x * x
é exatamente o mesmo que dizer
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
e sua função f
é substituído com a função with_logging
. Infelizmente, isso significa que se você diz
print(f.__name__)
será impresso with_logging
porque esse é o nome da sua nova função. De fato, se você olhar para a sequência de caracteres f
, ela ficará em branco porque with_logging
não possui nenhuma sequência e, portanto, a sequência que você escreveu não estará mais lá. Além disso, se você observar o resultado do pydoc para essa função, ele não será listado como tendo um argumento x
; em vez disso, será listado como take *args
e **kwargs
porque é isso que with_logging leva.
Se usar um decorador sempre significasse perder essas informações sobre uma função, seria um problema sério. É por isso que temos functools.wraps
. Isso pega uma função usada em um decorador e adiciona a funcionalidade de copiar sobre o nome da função wraps
, a sequência de documentos, a lista de argumentos etc. E, como ele próprio é um decorador, o código a seguir faz a coisa correta:
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'