Introduzione
Quando si parla di gestione delle eccezioni in Python, spesso si pensa subito a try, except e finally. Sono strumenti fondamentali, ma in molti casi esiste un approccio più elegante e sicuro: usare i context manager.
Un context manager è un oggetto che gestisce automaticamente l’apertura e la chiusura di una risorsa, anche se durante l’esecuzione si verifica un’eccezione. Il caso più comune è l’uso di with per lavorare con file, connessioni a database, lock di threading o risorse di rete.
Questo tutorial si concentra su un sotto-argomento molto pratico: come usare i context manager per gestire correttamente le eccezioni e prevenire problemi di risorse non rilasciate. È un tema utile perché riduce il codice boilerplate, rende il programma più leggibile e diminuisce il rischio di bug difficili da individuare.
Codice completo
from pathlib import Path
class FileLogger:
"""
Context manager personalizzato per scrivere log su file.
Garantisce che il file venga sempre chiuso, anche in caso di errore.
"""
def __init__(self, filename):
self.filename = filename
self.file = None
def __enter__(self):
# Apertura della risorsa
self.file = open(self.filename, "a", encoding="utf-8")
return self
def write(self, message):
if self.file is None:
raise RuntimeError("Il file non è aperto.")
self.file.write(message + "n")
def __exit__(self, exc_type, exc_value, traceback):
# Pulizia garantita della risorsa
if self.file:
self.file.close()
# Se è avvenuta un´eccezione, la registriamo in modo semplice
if exc_type is not None:
print(f"Errore intercettato: {exc_type.__name__}: {exc_value}")
# False = non sopprimere l´eccezione, lasciala propagare
return False
def process_data(input_path, log_path):
with FileLogger(log_path) as logger:
logger.write("Inizio elaborazione dati")
with open(input_path, "r", encoding="utf-8") as f:
for line_number, line in enumerate(f, start=1):
line = line.strip()
# Simuliamo una validazione semplice
if not line:
raise ValueError(f"Riga vuota trovata alla linea {line_number}")
number = int(line)
result = 100 / number
logger.write(f"Linea {line_number}: {number} - Spiegazione
Vediamo il funzionamento del codice passo per passo.
1. La classe FileLogger
Questa classe implementa il protocollo del context manager tramite due metodi speciali:
- __enter__: viene eseguito quando si entra nel blocco with. Qui apriamo il file in append.
- __exit__: viene eseguito quando si esce dal blocco, sia in caso di esecuzione normale sia in caso di eccezione. Qui chiudiamo sempre il file.
Questo è il punto chiave: la chiusura della risorsa è garantita, anche se il codice interno fallisce.
2. Il parametro exc_type in __exit__
Quando nel blocco with si verifica un’eccezione, Python passa a __exit__ informazioni utili:
- exc_type: il tipo dell’eccezione
- exc_value: il messaggio o valore associato
- traceback: lo stack trace
Nel nostro esempio stampiamo un messaggio semplice con il tipo di errore. È utile per il debug o per registrare eventi in un log.
3. Perché return False?
Restituire False da __exit__ significa che non vogliamo sopprimere l’eccezione. In altre parole, dopo aver fatto la pulizia, lasciamo che l’errore continui a propagarsi e venga gestito più in alto, nel blocco try/except del chiamante.
Se invece si restituisse True, l’eccezione verrebbe silenziata. Questo comportamento va usato con molta cautela, perché può nascondere bug importanti.
4. La funzione process_data
Questa funzione legge un file contenente numeri, uno per riga, e calcola 100 / numero. Durante l’elaborazione possono verificarsi più problemi:
- una riga vuota genera ValueError
- una stringa non convertibile in intero genererebbe ValueError
- un numero pari a zero causerebbe ZeroDivisionError
Il context manager del logger assicura che il file di log venga chiuso correttamente anche se una di queste eccezioni interrompe il flusso.
5. Il vantaggio rispetto a try/finally
Senza context manager, dovresti scrivere codice simile a questo:
file = open("app.log", "a", encoding="utf-8")
try:
file.write("Messaggion")
# operazioni...
finally:
file.close() Funziona, ma è più verboso e più facile da sbagliare. Con with, invece, il codice è più compatto e comunica chiaramente l’intenzione: “questa risorsa ha un ciclo di vita limitato e va gestita automaticamente”.
Best practice
- Usa sempre
withper le risorse esterne: file, socket, lock, connessioni, stream. - Non sopprimere eccezioni senza motivo: restituisci
Falsein__exit__se vuoi farle propagare. - Tieni separata la logica di business dalla gestione della risorsa: il context manager deve occuparsi soprattutto di apertura e chiusura.
- Preferisci context manager già pronti quando possibile, invece di reinventare la ruota.
- Registra gli errori in modo utile: se intercetti un’eccezione, fornisci un messaggio chiaro o un log strutturato.
- Evita di fare troppo lavoro in
__exit__: deve essere rapido e affidabile, soprattutto in caso di errore.
Riepilogo
I context manager sono uno strumento essenziale nella gestione delle eccezioni in Python. Ti permettono di scrivere codice più pulito, sicuro e leggibile, soprattutto quando lavori con risorse che devono essere sempre rilasciate.
In questo tutorial hai visto come creare un context manager personalizzato con __enter__ e __exit__, come intercettare le eccezioni durante l’uso di with e perché è spesso meglio di un semplice try/finally.
Se vuoi migliorare la qualità del tuo codice Python, questo è un passo molto importante: imparare a gestire bene le eccezioni non significa solo “catturare gli errori”, ma anche progettare il flusso del programma in modo robusto.
Approfondisci con risorse ufficiali
- Python documentation - context manager: documentazione ufficiale sul protocollo
withe sui metodi__enter__/__exit__. - Python documentation - built-in exceptions: elenco completo delle eccezioni integrate in Python.
- PEP 343: introduzione formale all’istruzione
withe ai context manager. - contextlib: modulo standard con strumenti utili come
contextmanager,ExitStacke altri helper avanzati.
