Gli errori sono solo valori con un brutto carattere
Quando qualcosa va storto in Python (una divisione per zero, la lettura di un file mancante, l'interpretazione di un numero non valido) l'interprete crea un oggetto eccezione e inizia a risalire lo stack delle chiamate finché qualcosa non lo cattura. Se niente lo cattura, il programma termina e stampa un traceback.
Le eccezioni di per sé non sono un male. Sono il modo in cui Python segnala "non posso continuare con questa operazione, ecco perché". Il tuo compito è decidere, caso per caso, da quali sai come riprenderti e quali invece devi lasciar salire.
La forma di base
try:apre il blocco di codice rischioso.except ValueError:cattura quella specifica eccezione se viene sollevata dentro iltry.- Se non scatta nessuna eccezione, l'
exceptviene saltato del tutto.
Esegui lo snippet con 42: funziona. Eseguilo con hello: parte il gestore.
Catturare eccezioni specifiche
Python ha una gerarchia di tipi di eccezione. Eccone alcune che incontrerai spesso:
ValueError: un valore era sbagliato in qualche modo (int("abc"), argomenti fuori intervallo).TypeError: è stato usato il tipo sbagliato ("hi" + 3).KeyError: una chiave di un dict non è stata trovata.IndexError: un indice di una sequenza era fuori intervallo.FileNotFoundError: un file non esiste.ZeroDivisionError: si è tentato di dividere per zero.AttributeError: un oggetto non ha l'attributo richiesto.
Cattura quella specifica che sai gestire:
Puoi catturare più eccezioni in una sola clausola passando una tupla:
Nota as e. Associa l'oggetto eccezione a e, così puoi esaminarne il messaggio o gli attributi.
Evita di catturare tutto
Un except: generico cattura letteralmente qualsiasi cosa, compresi KeyboardInterrupt (il tuo Ctrl-C) e le uscite a livello di sistema. Non usarlo.
except Exception: è un po' meglio, ma resta pericoloso: inghiotte bug che non avevi previsto e nasconde la vera origine dei problemi:
# Non farlo senza un ottimo motivo.
try:
do_something()
except Exception:
pass
La mossa giusta è quasi sempre catturare l'eccezione specifica da cui sai come riprenderti. Se un'eccezione inattesa arriva in cima al programma, il traceback ti dice esattamente cosa è andato storto: è una funzionalità, non un difetto.
else e finally
L'istruzione try ha altre due clausole facoltative:
elseviene eseguito se il bloccotryè terminato senza sollevare eccezioni.finallyviene eseguito in ogni caso, eccezione o no.
else è il posto più pulito per il codice "solo in caso di successo": non vuoi che un blocco try faccia più della parte che può davvero fallire. finally serve per la pulizia che deve avvenire anche se il try fallisce: chiudere una risorsa, rilasciare un lock, ripristinare uno stato.
Sollevare eccezioni tu stesso
Usa raise per segnalare un errore nel tuo codice:
Scegli un tipo di eccezione adatto a ciò che è andato storto. Prima di definire una tua classe, prova con quelli integrati: ValueError, TypeError, FileNotFoundError.
Definire un'eccezione personalizzata
Quando quelle integrate non esprimono il significato giusto, definisci un'eccezione personalizzata:
Eredita da Exception (o da un'eccezione integrata più specifica) e dai alla classe una docstring. Di solito non serve altro. Le eccezioni personalizzate permettono a chi chiama di catturare solo l'errore che ha senso nel suo dominio.
raise ... from ...: eccezioni concatenate
Quando un'eccezione ne scatena un'altra, conserva la catena:
from e collega l'errore originale. Quando stampa il traceback, Python li mostra entrambi: il ConfigError emerso in superficie e il FileNotFoundError che l'ha causato. Una traccia del genere è preziosissima durante il debug.
Context manager: il modo più pulito per fare pulizia
finally va bene, ma per risorse come i file un context manager (quello che usa with) è quasi sempre meglio:
# versione con finally
f = open("data.txt")
try:
data = f.read()
finally:
f.close()
# versione con with
with open("data.txt") as f:
data = f.read()
Entrambe sono sicure. La forma con with è più breve e si applica da sola. Ricorri a finally solo quando stai facendo qualcosa che la libreria standard non avvolge già in un context manager.
Quando non catturare
Catturare un'eccezione è una decisione: stai dicendo "questo lo so gestire". Se non sai gestirla, lasciala propagare. Codice come questo è quasi sempre un errore:
try:
do_work()
except Exception:
pass # ignora tutto in silenzio
Zittire gli errori rende invisibili i bug. Meglio un crash rumoroso che andare avanti zoppicando in uno stato incoerente.
Riassumendo
try/exceptti permette di gestire gli errori da cui puoi riprenderti.- Cattura eccezioni specifiche, non
Exceptionalla cieca. raisesegnala errori nel tuo codice.- I blocchi
withsostituiscono la maggior parte della pulizia confinally. - Nel dubbio, lascia propagare l'eccezione.
Prossimo argomento: un giro tra gli errori specifici che Python solleva più spesso (KeyError, ValueError, ModuleNotFoundError e qualche altro) più le abitudini di debug che li risolvono in fretta.
Domande frequenti
Come gestisco gli errori in Python?
Metti il codice rischioso in un blocco try e cattura l'eccezione specifica in un blocco except: try: risky() except ValueError: .... Il blocco facoltativo else viene eseguito se non si è verificata nessuna eccezione; finally viene eseguito in ogni caso, per la pulizia.
Conviene catturare Exception per stare tranquilli?
No. Un except: generico o un except Exception: nascondono bug che non avevi previsto. Cattura l'eccezione specifica da cui sai come riprenderti, e lascia propagare tutto il resto così vedi il vero problema.
Qual è la differenza tra raise e raise from?
raise NewError(...) solleva una nuova eccezione. raise NewError(...) from original mantiene collegata l'eccezione originale come causa, e Python la mostra nel traceback. Usa from quando un errore di basso livello ha scatenato un errore di livello più alto che vuoi far emergere.