I decoratori sono apparsi in Python come zucchero sintattico dalla versione 2.4, per semplificare il lavoro con le funzioni di ordine superiore — quelle che accettano o restituiscono altre funzioni. L'evoluzione degli approcci per estendere la funzionalità delle funzioni ha portato a un formato conciso ed espressivo — annotazioni tramite @decorator.
Nei grandi progetti è spesso necessario modificare o incapsulare funzioni con una logica aggiuntiva: registrazione, controllo accessi, caching, misurazione del tempo di esecuzione. Senza decoratori, era necessario chiamare manualmente le funzioni di incapsulamento, gonfiando il codice.
I decoratori consentono di estrarre aspetti ripetitivi in avvolgimenti separati, migliorando così la leggibilità e il riutilizzo del codice. Con @decorator è possibile aggiungere elegantemente funzionalità a funzioni e metodi:
Esempio di codice:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Stampa quanto tempo ha impiegato l'esecuzione
Caratteristiche chiave:
Possono i decoratori cambiare la firma della funzione avvolta?
Spesso si crede erroneamente che la firma della funzione non cambi con il decoratore. In realtà, senza l'uso del modulo functools.wraps, le informazioni metainformatiche vanno perse, il che può causare errori indesiderati nella documentazione automatica o nell'introspezione.
Esempio di codice:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
Possono i decoratori essere parametrizzati?
Spesso si risponde che non è possibile. In realtà, è possibile creare un decoratore parametrizzato tramite un ulteriore livello di annidamento — una funzione che restituisce un decoratore.
Esempio di codice:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
È possibile applicare più decoratori a una funzione? Qual sarà l'ordine di esecuzione?
Si crede spesso erroneamente che l'ordine non sia importante o che coincida con l'ordine dei decoratori nel codice. In realtà, il decoratore più basso viene applicato per primo, poi il successivo e così via verso l'alto.
Esempio di codice:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
La registrazione del tempo di esecuzione è stata aggiunta manualmente a 10 funzioni, per copia e incolla.
Pro:
Contro:
Tutta la logica di temporizzazione è stata estratta in un decoratore, tutte le funzioni in cui è necessaria questa metrica sono state incapsulate con il decoratore @timing_decorator.
Pro:
Contro:
functools.wraps, se non si presta attenzione;