Articolo originale: Error Handling in Python – try, except, else, & finally Explained with Code Examples

Di recente, il mio manager mi ha incaricato di creare un report automatico. Ho progettato il report affinché fosse semplice. Includeva un po' di numeri da un database e alcune operazioni matematiche basilari. Ero entusiasta di poter finalmente mettere in mostra in azienda le mie incredibili abilità con Python.

Ho completato e consegnato il prodotto. Tutto andava alla grande. O almeno, fino a due settimane più tardi. Il mio report ha cominciato a fallire improvvisamente a causa di un errore di divisione per zero. È il momento di far partire la risata registrata.

La mia breve storia non è particolarmente dettagliata, ma dovrebbe enfatizzare l'importanza di gestire casi limite ed errori quando si scrivono programmi. Questo report sarebbe dovuto essere un'opportunità per mettere in mostra la mia competenza in Python, invece, ne è risultata una figuraccia piuttosto imbarazzante.

Perciò, prendiamoci un momento per imparare le basi della gestione degli errori utilizzando la libreria standard di Python. Metterò in evidenza alcune delle cose di cui hai bisogno per iniziare.

Prima di iniziare a gestire eccezioni, dovresti avere una buona comprensione dei fondamenti di Python. Avrai bisogno di sapere perché le eccezioni vengono generate per poterle trattare!

Ecco cosa tratteremo:

  1. Istruzioni try ed except in Python
  2. Esecuzione condizionale con la clausola else
  3. Eccezioni predefinite
  4. Eccezioni personalizzate
  5. Considerazioni sulle prestazioni

Istruzioni try ed except in Python

Le istruzioni try ed except sono i metodi principali per trattare le eccezioni. Ecco un esempio:

x = 0
try:
    print(5 / x)
except ZeroDivisionError:
    print("Qualcosa è andato storto")

# Qualcosa è andato storto

Rivediamo il codice qui sopra per essere sulla stessa lunghezza d'onda:

  1. La riga 1 assegna il valore 0 alla variabile x
  2. Le righe 2 e 3 aprono una clausola try e tentano di dividere 5 per la variabile x
  3. Le righe 4 e 5 aprono una clausola except per qualsiasi errore di divisione per zero (ZeroDivisionError) e istruiscono il programma a stampare un messaggio qualora si tentasse di dividere per 0

Probabilmente avrai notato il problema: la variabile x ha valore 0, e sto cercando di dividere 5 per x . Neppure i migliori matematici del mondo sono in grado di dividere per 0, e nemmeno Python può farlo. Quindi, che succede?

Se non ci occupassimo dell'errore, il programma terminerebbe immediatamente, non appena cercasse di dividere 5 per x. Dato che i programmi non sanno cosa fare con le eccezioni senza istruzioni esplicite, abbiamo creato la clausola except nella riga 4 e fornito al programma i passi da seguire in caso di divisione per 0.

Questa è l'intera idea dietro la gestione delle eccezioni: occorre dire al programma cosa fare quando si verifica un errore che non può semplicemente ignorare. Vediamo come funzionano le clausole try ed except.

Analisi dell'istruzione try

Le clausole try ed except seguono un modello che ti permette di gestire in modo affidabile i problemi nel tuo codice. Vediamo come funziona.

Il primo passo è tentare l'esecuzione del codice nella clausola try.

Dopodiché, abbiamo tre possibilità:

Nessun errore nella clausola try

Se il codice nella clausola try viene eseguito senza nessun errore, il programma:

  1. Esegue la clausola try
  2. Salta tutte le clausole except
  3. Prosegue con la sua normale esecuzione
x = 1
try:
    print(5 / x)
except ZeroDivisionError:
    print("Qualcosa è andato storto")

print("Sto eseguendo dopo la clausola try!")

# 5.0
# Sto eseguendo dopo la clausola try!

In questo esempio modificato, puoi vedere che non ci sono problemi nella clausola try (righe 3 e 4). Il codice verrà eseguito, la clausola except verrà saltata e il programma riprenderà l'esecuzione dopo la terminazione delle istruzioni try ed except.

Errori nella clausola try e l'eccezione è specificata

Se il codice nella clausola try genera un'eccezione e il tipo di eccezione viene specificato dopo qualsiasi parola chiave con except , il programma:

  1. Salta il codice rimanente nella clausola try
  2. Esegue il codice nella clausola except corrispondente
  3. Prosegue con la sua normale esecuzione
x = 0
try:
    print(5 / x)
except ZeroDivisionError:
    print("Qualcosa è andato storto")

print("Sto eseguendo dopo la clausola try!")

# Qualcosa è andato storto
# Sto eseguendo dopo la clausola try!

Tornando al mio primo esempio, ho cambiato la nostra variabile x di nuovo al valore 0 e ho provato a dividere 5 per x. Ciò produce un  errore di divisione per zero (ZeroDivisionError). Dato che l'istruzione except specifica questo tipo di eccezione, il codice in quella clausola viene eseguito prima che il programma riprenda la sua normale esecuzione.

Errori nella clausola Try e l'Eccezione NON è Specificata

Infine, se il programma genera un'eccezione nella clausola try, ma l'eccezione non viene specificata in nessuna istruzione except, allora il programma:

  1. Interrompe l'esecuzione del programma e dà errore
x = 0
try:
    print(5 / y)
except:
    print("Qualcosa è andato storto")

print("Sto eseguendo dopo la clausola try!")

# NameError: name 'y' is not defined (il nome 'y' non è definito)

Nell'esempio precedente, sto cercando di dividere 5 per la variabile y, che non esiste. Questo genera un errore di nome (NameError). Non abbiamo specificato al programma come gestire i NameError, perciò l'unica opzione è quella di terminare.

Pulizia

try ed except sono gli strumenti principali della gestione degli errori, ma esiste anche una clausola opzionale che puoi usare, chiamata finally. La clausola finally verrà sempre eseguita, che ci sia un errore o meno.

x = 0
try:
    print(5 / x)
except ZeroDivisionError:
    print("Sono la clausola except!")
finally:
    print("Sono la clausola finally!")

print("Sto eseguendo dopo la clausola try!")

# Sono la clausola except!
# Sono la clausola finally!
# Sto eseguendo dopo la clausola try!

In questo esempio, ho creato il nostro amato ZeroDivisionError. Puoi vedere che l'ordine di esecuzione è:

  1. La clausola except
  2. La clausola finally
  3. Qualsiasi codice successivo

Una volta che correggiamo il codice nella clausola try in modo che non generi più errori, vedrai comunque un ordine di esecuzione simile. Al posto della clausola except, verrà eseguita la clausola try.

x = 1
try:
    print(5 / x)
except ZeroDivisionError:
    print("Sono la clausola except!")
finally:
    print("Sono la clausola finally!")

print("Sto eseguendo dopo la clausola try!")

# 5.0
# Sono la clausola finally!
# Sto eseguendo dopo la clausola try!

Noterai che l'unica differenza è che la clausola try viene eseguita con successo perché non vengono generate eccezioni. La clausola finally e il codice successivo vengono eseguiti come previsto.

Questo è utile in alcuni casi in cui vuoi dare una "ripulita" indipendentemente dall'esito delle clausole try e except. Azioni come chiudere connessioni, chiudere file e fare logging sono ottimi candidati per la clausola finally.

Esecuzione condizionale con la clausola else

Un'altra clausola opzionale è la clausola else. La clausola else è semplice: se il codice nella clausola try viene eseguito senza generare errori, allora anche il codice nella clausola else verrà eseguito.

x = 1
try:
    print(5 / x)
except ZeroDivisionError:
    print("Sono la clausola except!")
else:
    print("Sono la clausola else!")
finally:
    print("Sono la clausola finally!")

print("Sto eseguendo dopo la clausola try!")

# 5.0
# Sono la clausola else!
# Sono la clausola finally!
# Sto eseguendo dopo la clausola try!

L'ordine di esecuzione per questo esempio è:

  1. La clausola try
  2. La clausola else
  3. La clausola finally
  4. Qualsiasi codice dopo

Se dovessimo incontrare un'eccezione o un errore nella clausola try, la clausola else verrebbe ignorata.

x = 0
try:
    print(5 / x)
except ZeroDivisionError:
    print("Sono la clausola except!")
else:
    print("Sono la clausola else!")
finally:
    print("Sono la clausola finally!")

print("Sto eseguendo dopo la clausola try!")

# Sono la clausola except!
# Sono la clausola finally!
# Sto eseguendo dopo la clausola try!

Eccezioni predefinite

Mi hai visto scrivere a proposito di due diversi errori finora: NameError e ZeroDivisionError. Ma se avessimo bisogno di altre eccezioni?

Esiste un'intera lista di eccezioni predefinite in Python che fanno parte della libreria standard. Probabilmente coprono quasi tutte le necessità che potresti avere nella gestione di errori ed eccezioni.

Ecco solo alcune che potrebbero essere importanti:

  • KeyError – Una chiave non viene trovata in un dizionario
  • IndexError – L'indice è fuori dai limiti stabiliti di un oggetto iterabile
  • TypeError – Una funzione o un'operazione è stata usata su un tipo di oggetto sbagliato
  • OSError – Errori generali del sistema operativo

Ce ne sono molte altre, che puoi trovare nella documentazione di Python. Consiglio di darci un'occhiata. Non solo diventerai più bravo a gestire gli errori, ma esplorerai anche cosa può effettivamente andare storto nei i tuoi programmi Python.

Eccezioni personalizzate

Se hai bisogno di funzionalità estese, puoi anche definire delle eccezioni personalizzate.

class ForError(Exception):
    def __init__(self, message):
        self.message = message

    def foo(self):
        print("bar")

Nell'esempio sopra, ho creato una nuova classe e l'ho estesa dalla classe Exception. Ora posso scrivere una funzionalità personalizzata e trattare questa eccezione come qualsiasi altro oggetto.

try:
    raise FooError("Questo è un errore di test")
except FooError as e:
    e.foo()

# bar

Qui ho generato di proposito il mio nuovo FooError. Ho catturato FooError e gli assegno l'alias e. Ora posso accedere al mio metodo foo() che ho costruito nella classe che ho creato.

Questo apre un mondo di possibilità quando si ha a che fare con gli errori. Log personalizzati, tracciamenti più dettagliati, o qualsiasi altra cosa di cui hai bisogno, tutto può essere codificato e creato.

Considerazioni sulle prestazioni

Ora che conosci le basi di try, except e degli oggetti eccezione, puoi cominciare a considerare di usarli nel tuo codice per gestire gli errori in modo elegante. Ma ci sono impatti significativi sulle prestazioni del codice?

La risposta breve è no. Con il rilascio di Python 3.11, non c'è praticamente nessuna riduzione di velocità nell'uso delle istruzioni try ed except quando non vengono sollevate eccezioni.

La cattura degli errori ha causato effettivamente qualche rallentamento. Ma in generale, è meglio catturare questi errori piuttosto che far crashare l'intero programma.

Nelle versioni precedenti di Python, usare le clausole try ed except causava del tempo di esecuzione aggiuntivo. Tieni presente questo dettaglio se non hai la versione aggiornata.

Ricapitolando

Grazie per aver letto fino a qui. Il futuro te e i tuoi futuri clienti ti ringrazieranno per la gestione degli errori.

Abbiamo esaminato le clausole try, except, else, e finally, il loro ordine di esecuzione e in quali circostanze vengono eseguite. Abbiamo anche visto le basi per creare eccezioni personalizzate.

La cosa più importante da ricordare è che le clausole try ed except sono i modi principali per gestire gli errori e dovresti usarle ogni volta che hai del codice rischioso e che potrebbe generare degli errori.

Inoltre, tieni a mente che la cattura degli errori renderà il tuo codice più resiliente e ti farà sembrare un programmatore decisamente migliore.