Introduzione
Quando si parla di decoratori in Python, spesso si pensa a validazione, logging o caching. Un uso molto pratico e meno “da manuale” è però la creazione automatica di context manager, cioè oggetti che gestiscono apertura e chiusura di risorse con la sintassi with.
Questo approccio è utile quando vuoi applicare la stessa logica di setup e cleanup a molte funzioni senza ripeterla ogni volta. Per esempio: aprire e chiudere una connessione a un database, misurare l’uso di una risorsa temporanea, attivare/disattivare una modalità di debug, oppure gestire un lock in modo sicuro.
In questo tutorial vedremo come combinare decoratori e metaprogrammazione per costruire un decoratore che esegue una funzione dentro un contesto automatico, senza costringere il chiamante a scrivere codice boilerplate.
Codice completo
from functools import wraps
from contextlib import contextmanager
from datetime import datetime
import time
@contextmanager
def traccia_esecuzione(nome_operazione: str):
"""
Context manager che simula:
- apertura di una risorsa
- esecuzione del blocco
- chiusura della risorsa
"""
inizio = datetime.now()
print(f"[START] {nome_operazione} - {inizio.isoformat(timespec=´seconds´)}")
try:
yield
finally:
fine = datetime.now()
durata = (fine - inizio).total_seconds()
print(f"[END] {nome_operazione} - durata: {durata:.3f}s")
def con_contesto(factory_contesto):
"""
Decoratore parametrico che esegue la funzione dentro un context manager.
factory_contesto: funzione che restituisce un context manager.
"""
def decoratore(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Usiamo il context manager generato dalla factory
with factory_contesto(func.__name__):
return func(*args, **kwargs)
return wrapper
return decoratore
@con_contesto(traccia_esecuzione)
def importa_dati_simulati():
"""Simula un´operazione lenta, come il caricamento di dati."""
time.sleep(1.2)
return {"righe": 1500, "stato": "ok"}
@con_contesto(traccia_esecuzione)
def calcola_report():
"""Simula un´elaborazione più breve."""
time.sleep(0.5)
return "report_generato.pdf"
if __name__ == "__main__":
risultato1 = importa_dati_simulati()
print("Risultato 1:", risultato1)
risultato2 = calcola_report()
print("Risultato 2:", risultato2) Spiegazione
Il cuore dell’esempio è il decoratore con_contesto. Invece di limitarsi a eseguire una funzione prima o dopo un’altra, questo decoratore la avvolge dentro un blocco with. In altre parole, la funzione decorata viene eseguita all’interno di un contesto gestito automaticamente.
1. Il context manager
La funzione traccia_esecuzione è decorata con @contextmanager del modulo contextlib. Questo permette di scrivere un context manager usando una funzione generatore, invece di definire una classe con i metodi __enter__ e __exit__.
La logica è semplice:
- prima del
yieldeseguiamo il codice di inizializzazione; - dopo il
yieldeseguiamo il cleanup; - il blocco
finallygarantisce la chiusura anche in caso di eccezioni.
2. Il decoratore parametrico
con_contesto è un decoratore parametrico: riceve in ingresso una funzione che crea il contesto, e restituisce un vero decoratore. Questa struttura è molto comune quando vuoi rendere riutilizzabile una logica trasversale.
La parte importante è questa:
@wraps(func)preserva nome, docstring e metadati della funzione originale;wrapper(*args, **kwargs)rende il decoratore compatibile con qualsiasi firma;with factory_contesto(func.__name__):crea il contesto usando il nome della funzione come etichetta.
3. Cosa succede quando chiami la funzione decorata
Quando esegui importa_dati_simulati(), non stai chiamando direttamente la funzione originale. In realtà il flusso è questo:
- entra nel context manager e stampa
[START]; - esegue il corpo della funzione;
- esce dal contesto e stampa
[END]con la durata.
Questo è un esempio concreto di metaprogrammazione: il comportamento della funzione viene modificato a runtime senza cambiare il codice interno della funzione stessa.
4. Perché è utile davvero
Questo pattern è molto utile in situazioni reali:
- gestione automatica di transazioni;
- apertura e chiusura di connessioni a risorse esterne;
- attivazione temporanea di configurazioni o flag;
- tracciamento strutturato di operazioni critiche;
- isolamento di operazioni che richiedono cleanup garantito.
Rispetto a un decoratore classico che si limita a fare logging, qui il vantaggio è che il contesto è un oggetto riutilizzabile, e il decoratore diventa un livello di astrazione sopra di esso.
Best practice
- Usa
wrapssempre: senza di esso perdi il nome originale della funzione, la docstring e altri metadati utili per debugging e introspezione. - Separa responsabilità: il context manager deve occuparsi del ciclo di vita della risorsa; il decoratore deve solo applicarlo alla funzione.
- Gestisci bene le eccezioni: il cleanup deve stare in un blocco che venga eseguito anche se la funzione fallisce.
- Evita side effect nascosti: un decoratore che cambia troppo il comportamento può rendere il codice difficile da leggere. Usalo quando il beneficio è chiaro.
- Rendi il contesto configurabile: passare parametri come il nome dell’operazione, il livello di log o il tipo di risorsa rende il pattern più utile in progetti reali.
- Non abusare della metaprogrammazione: se una soluzione semplice basta, preferiscila. I decoratori devono semplificare, non creare magia inutile.
Riepilogo
In questo tutorial abbiamo visto un uso pratico e potente dei decoratori: eseguire funzioni dentro context manager automatici. Questo approccio unisce la flessibilità dei decoratori con la sicurezza del pattern with, permettendo di centralizzare setup e cleanup in un unico punto.
I vantaggi principali sono:
- meno codice ripetuto;
- maggiore coerenza nella gestione delle risorse;
- funzioni più pulite e focalizzate sulla logica di business;
- composizione elegante tra decoratori e context manager.
È una tecnica particolarmente adatta quando vuoi applicare una “cornice” comune a più funzioni, mantenendo il codice leggibile e sicuro.
Approfondisci con risorse ufficiali
- functools.wraps — documentazione ufficiale Python: utile per preservare i metadati delle funzioni decorate.
- contextlib.contextmanager — documentazione ufficiale Python: per creare context manager con i generatori.
- PEP 343 — il documento che introduce l’istruzione
withe i context manager in Python. - Python Docs: context managers — guida ufficiale su come usare e creare context manager.
- Python Docs: decorators — riferimento ufficiale per comprendere la sintassi e il comportamento dei decoratori.
