Hai mai voluto misurare quanto ci mette una funzione, e sei finito a incollare inizio = time.time() all’inizio e print(time.time() - inizio) alla fine? E poi l’hai fatto in un’altra funzione, e in un’altra ancora, finché metà del tuo script non era cronometro e solo l’altra metà lavoro vero? Quel prurito che senti — «sto ripetendo la stessa cosa ovunque» — è esattamente il problema che i decorator risolvono. E le property risolvono il gemello di quel problema, sul fronte degli attributi. Oggi impari entrambi, con codice che puoi copiare ed eseguire subito.
Tipi, strutture dati, controllo di flusso e funzioni: le basi di Python sono nel libro, in un percorso che si legge in ordine.
Prerequisiti in una riga: se non ricordi bene le funzioni con *args e **kwargs, ti basta sapere che sono il modo di scrivere una funzione che accetta qualunque combinazione di argomenti e li passa avanti; e se non ricordi le classi, pensale come stampi che producono oggetti con dati e metodi. Ambiente: Python 3.12 in un virtualenv (python -m venv .venv o uv venv), un editor come VS Code o PyCharm. Nessuna libreria esterna: oggi basta la standard library.
Prima le fondamenta: le funzioni sono oggetti
Il trucco che rende possibili i decorator è una proprietà di Python spesso trascurata: le funzioni sono oggetti come tutti gli altri. Puoi assegnarle a variabili, metterle in una lista, passarle come argomento e — soprattutto — restituirle da un’altra funzione:
def saluta():
return "Ciao!"
copia = saluta # nessuna parentesi: NON la chiamo, la passo
print(copia()) # Ciao! — la variabile ora "è" la funzione
def fabbrica():
def interna():
return "Vengo da dentro"
return interna # restituisco una funzione, non il suo risultato
f = fabbrica()
print(f()) # Vengo da dentro
Se questo ti è chiaro, i decorator sono a un passo. Se ti sembra strano, rileggilo una volta: tutto il resto dell’articolo poggia su queste dieci righe.
Il tuo primo decorator: un cronometro riutilizzabile
Un decorator è come la cover del telefono: il telefono dentro resta identico, ma tutto ciò che gli arriva passa prima dalla cover, e la cover può aggiungere qualcosa (protezione, un anello, un portacarte) senza che il telefono ne sappia nulla. In codice: una funzione che prende una funzione, la avvolge in un’altra funzione che fa qualcosa prima e dopo, e restituisce la versione avvolta.
# cronometro.py — Python 3.12, solo standard library
import time
from functools import wraps
def cronometro(funzione):
@wraps(funzione) # preserva nome e docstring originali
def avvolta(*args, **kwargs):
inizio = time.perf_counter() # PRIMA: avvia il timer
risultato = funzione(*args, **kwargs) # la funzione originale
durata = time.perf_counter() - inizio # DOPO: ferma e stampa
print(f"{funzione.__name__} ha impiegato {durata:.4f} s")
return risultato
return avvolta
@cronometro
def somma_quadrati(n):
"""Somma dei quadrati da 0 a n."""
return sum(i * i for i in range(n))
totale = somma_quadrati(10_000_000)
# stampa: somma_quadrati ha impiegato 0.61xx s (dipende dalla macchina)
La riga @cronometro è solo zucchero sintattico per somma_quadrati = cronometro(somma_quadrati): prendi la funzione, la passi al decorator, e riassegni al vecchio nome la versione avvolta. Da quel momento ogni chiamata a somma_quadrati passa dalla «cover».
Due dettagli che distinguono il codice da tutorial dal codice da produzione: time.perf_counter() invece di time.time(), perché è il timer ad alta risoluzione pensato per misurare durate; e @wraps(funzione) da functools, che copia nome e docstring della funzione originale su quella avvolta. Senza wraps, somma_quadrati.__name__ risponderebbe "avvolta" — e strumenti come il debugger o la generazione automatica di documentazione si confonderebbero.
La funzione avvolta: gli argomenti entrano nel decorator, che esegue il suo codice prima e dopo la funzione originale — la quale resta intatta al centro, ignara di tutto.
Un decorator non modifica la funzione: la avvolge. È la differenza tra ricamare una giacca e mettersi un cappotto sopra.
property: attributi con il buttafuori
Passiamo al secondo strumento. Immagina la configurazione di un esperimento di machine learning: la frazione di dati da riservare al test deve stare tra 0 e 1, esclusi. Con un attributo normale, niente impedisce a qualcuno (spesso: te stesso, alle 23:40) di scrivere config.test_size = 25 intendendo 25%. L’errore esplode molto più avanti, in un punto lontano dalla causa.
property mette un buttafuori davanti all’attributo: dall’esterno sembra un normale accesso, ma ogni lettura e ogni scrittura passano da un metodo che può controllare, calcolare o rifiutare. E — nota il ritorno di fiamma — property si usa proprio con la sintassi dei decorator:
class ConfigEsperimento:
def __init__(self, test_size=0.25):
self.test_size = test_size # passa già dal setter: validato!
@property
def test_size(self):
"""Frazione del dataset riservata al test (0-1 esclusi)."""
return self._test_size # underscore: attributo "interno"
@test_size.setter
def test_size(self, valore):
if not 0 < valore < 1:
raise ValueError(
f"test_size deve stare tra 0 e 1, ricevuto {valore}. "
"Per il 25% usa 0.25, non 25."
)
self._test_size = valore
@property
def train_size(self):
# attributo CALCOLATO: sempre coerente, mai da aggiornare a mano
return 1 - self._test_size
config = ConfigEsperimento()
print(config.train_size) # 0.75 — calcolato al volo
config.test_size = 0.2 # ok, il buttafuori lascia passare
config.test_size = 25 # ValueError, con messaggio che spiega
Nota due cose. Primo: chi usa la classe scrive config.test_size, senza parentesi, come un attributo qualsiasi — l’eleganza sta proprio qui, la validazione è invisibile. Secondo: train_size è una property in sola lettura che si calcola da test_size; non può mai essere «dimenticata» fuori sincrono, perché non esiste come dato, esiste come regola.
Quando NON usarli
Onestà prima dell’entusiasmo: entrambi gli strumenti si prestano all’abuso.
- Non decorare per il gusto di decorare. Un decorator ha senso quando lo stesso comportamento (misurare, loggare, riprovare, mettere in cache) va applicato a più funzioni. Se ti serve in una funzione sola, tre righe inline sono più chiare.
- Non reinventare la standard library. Prima di scrivere un decorator di caching, guarda
functools.lru_cacheefunctools.cache: sono già lì, testati e veloci. - Niente property «di riflesso» su ogni attributo. Python non è Java: se un attributo non ha bisogno di validazione né di calcolo, lascialo attributo semplice. Potrai sempre trasformarlo in property domani senza cambiare il codice che lo usa — è questo il vero superpotere: puoi permetterti di partire semplice.
- Mai lavoro pesante dentro una property. Chi legge
config.train_sizesi aspetta un accesso istantaneo: se dietro c’è una query al database o un calcolo di dieci secondi, hai teso una trappola. In quel caso usa un metodo esplicito, che con le parentesi dichiara «qui succede qualcosa».
Dove li ritroverai
Da qui in avanti i decorator ti verranno incontro ovunque: @staticmethod e @classmethod nelle classi, @pytest.fixture nei test, @app.route nei framework web come Flask e FastAPI, @torch.no_grad() quando arriverai al deep learning. Non dovrai quasi mai scriverne di complessi: dovrai però riconoscere al volo cosa fanno, e da oggi puoi. La forma è sempre la stessa: qualcosa che avvolge la tua funzione per aggiungerle un comportamento senza toccarla.
Prova tu
Esercizio concreto in due parti. Prima: scrivi un decorator @riprova che, se la funzione avvolta solleva un’eccezione, la richiama fino a 3 volte prima di arrendersi e rilanciare l’errore, stampando a ogni tentativo «tentativo 2 di 3…». Testalo su una funzione che fallisce a caso (usa random.random() < 0.5 per decidere). Seconda: aggiungi a ConfigEsperimento una property random_state che accetta solo interi non negativi o None. Se completi entrambe, hai capito il 90% di quello che serve nella pratica.
Per approfondire: la documentazione ufficiale di functools (docs.python.org) merita una lettura integrale — è breve e piena di attrezzi pronti; per andare a fondo su decorator con argomenti e property avanzate, il capitolo «Decorators and Closures» di Fluent Python di Luciano Ramalho (2ª edizione, O’Reilly) è il riferimento migliore che conosca.

I commenti sono riservati agli iscritti.
Accedi per commentare