Quando usi un decoratore, stai sostituendo una funzione con un'altra. In altre parole, se hai un decoratore
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
poi quando dici
@logged
def f(x):
"""does some math"""
return x + x * x
è esattamente come dire
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
e la tua funzione f
viene sostituita con la funzione with_logging
. Sfortunatamente, questo significa che se poi lo dici
print(f.__name__)
verrà stampato with_logging
perché è il nome della tua nuova funzione. In effetti, se guardi il docstring f
, sarà vuoto perché with_logging
non ha docstring, e quindi il docstring che hai scritto non ci sarà più. Inoltre, se si guarda al risultato pydoc per quella funzione, non verrà elencato come prendendo un argomento x
; invece verrà elencato come take *args
e **kwargs
perché è quello che richiede with_logging.
Se usare un decoratore significava sempre perdere queste informazioni su una funzione, sarebbe un problema serio. Ecco perché abbiamo functools.wraps
. Questo prende una funzione usata in un decoratore e aggiunge la funzionalità di copia su nome della funzione, docstring, lista di argomenti, ecc. E poiché wraps
è esso stesso un decoratore, il seguente codice fa la cosa giusta:
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'