Spiegazione dei decoratori Python: da @property a Retry Logic in 15 minuti (2026)
I decoratori sono la caratteristica che separa "conosce Python" da "legge Python fluentemente". Sembrano magici, ma sono meccanici: @decorator su una funzione è esattamente f = decorator(f). Questo è tutto. Tutto il resto in questo tutorial è quella linea applicata in forme diverse.
La teoria della linea unica
Quando scrivi:
Python lo trasforma in:
Questa è l'intera meccanica. @decorator_name sopra un def esegue def_name = decorator_name(def_name) dopo la definizione. Se lo interiorizzi, i decoratori smetteranno di essere misteriosi per sempre.
Scrivere il tuo primo decoratore (60 secondi)
Un decoratore è qualsiasi chiamabile che accetta una funzione e restituisce una funzione. La forma tipica:
Il *args, **kwargs nel wrapper è un modello difensivo: consente al decoratore di lavorare su qualsiasi funzione indipendentemente dalla firma. Saltalo e otterrai "TypeError: wrapper() accetta 0 argomenti posizionali ma ne sono stati forniti 2" nel momento in cui qualcuno decora una funzione con parametri.
Correzione di @functools.wraps
Un bug sottile: dopo la decorazione, add.__name__ è "wrapper", non "add". Anche le docstring e i suggerimenti sul tipo scompaiono. La correzione è un'importazione:
Regola: ogni decoratore che scrivi dovrebbe usare `@functools.wraps(fn)`. Costa una riga e previene un'intera categoria di problemi di debug (introspezione, dispositivi pytest, formattatori logging, pickle — tutti si interrompono silenziosamente senza di essa).
I quattro decoratori che vedrai in ogni codebase
1. @property — attributi calcolati
@property rende un metodo richiamabile come se fosse un attributo. Perfetto per valori derivati di sola lettura. C'è anche @area.setter per il percorso di scrittura, ma il 90% degli usi di @property sono di sola lettura.
2. @staticmethod e @classmethod
@classmethod ottiene cls (la classe stessa) e viene utilizzato per costruttori alternativi. @staticmethod non ottiene nulla di speciale ed è una semplice funzione che sembra vivere all'interno dello spazio dei nomi della classe.
3. @functools.cache — memorizzazione
Uno dei decoratori stdlib più potenti. Memorizza automaticamente la funzione in base ai suoi argomenti. Richiede che tutti gli argomenti siano hashable (nessuna lista/dettato come argomenti - usa tuple).
4. @dataclasses.dataclass — eliminazione del boilerplate
@dataclass legge le annotazioni del tipo a livello di classe e genera automaticamente __init__, __repr__, __eq__ e (con frozen=True) __hash__. È il più grande miglioramento della qualità della vita in Python 3.7+.
Scrivere un decoratore @retry (esempio reale)
Un decoratore che riprova una funzione in caso di fallimento con backoff esponenziale:
Notare le tre funzioni nidificate: retry accetta gli argomenti di configurazione e restituisce decorator; decorator accetta la funzione e restituisce wrapper; wrapper esegue il ciclo di tentativi. Questo modello a tre strati è l'aspetto di qualsiasi decoratore con argomenti: memorizza la forma, non l'esempio specifico.
Decoratori basati su classi (rari ma utili)
Un decoratore è qualsiasi chiamabile. Anche le classi con __call__ funzionano:
Da utilizzare quando il decoratore deve mantenere uno stato che sopravvive a una singola chiamata (un contatore, una cache, un bucket con limite di velocità).
Insidie comuni
1. Dimenticando `@functools.wraps`: l'introspezione si interrompe silenziosamente.
2. Argomenti predefiniti mutabili nel decoratore stesso — anche qui il classico bug def wrapper(x, cache=[]).
3. Metodi di decorazione senza accettare `self`: ricorda che self è solo un argomento posizionale; *args, **kwargs lo capisce bene.
4. L'ordine è importante quando si impila: @a\n@b\ndef f() è f = a(b(f)). Leggi gli stack dal basso verso l'alto.
Pratica
Scrivi un decoratore "@timing" che stampi quanto tempo impiega l'esecuzione della funzione racchiusa, quindi applicalo a una funzione che calcola "sum(range(10_000_000))". L'intero decoratore è composto da 8 righe con functools.wraps + time.perf_counter.
Una volta interiorizzato che @x è f = x(f), i decoratori diventano una delle funzionalità più produttive di Python. Sono ovunque nelle basi di codice reali: percorsi Flask/FastAPI (@app.route), dispositivi pytest (@pytest.fixture), visualizzazioni Django (@login_required), validatori Pydantic (@field_validator). Imparare a scriverli da solo sblocca la capacità di leggere ognuno di questi.
Passaggio successivo: 10 lezioni pratiche sul decoratore — crea @retry, @memoise, @rate_limit e @requires_auth da zero con Python reale in esecuzione nel tuo browser.