Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Gestione delle eccezioni

Come vengono gestite le eccezioni nel linguaggio di programmazione Python: una panoramica sull'uso delle parole chiave try ed except.
Come vengono gestite le eccezioni nel linguaggio di programmazione Python: una panoramica sull'uso delle parole chiave try ed except.
Link copiato negli appunti

In Python gli errori vengono riportati e gestiti usando le eccezioni. Ogni volta che un programma esegue un'operazione non valida, viene generata un'eccezione. Al contrario dei normali valori di ritorno che possono essere restituiti usando return, le eccezioni si propagano automaticamente finchè vengono catturate e gestite; se non vengono gestite, il programma mostra un messaggio di errore e termina. In questa lezione vedremo più in dettaglio come catturare, gestire, e riportare eccezioni.

Tipi di eccezioni

Durante le precedenti lezioni abbiamo già visto diverse eccezioni, come ad esempio SyntaxError, NameError, ValueError, TypeError, ecc.:

>>> print 'Hello World!'
  File "<stdin>", line 1
    print 'Hello World!'
                       ^
SyntaxError: Missing parentheses in call to 'print'
>>> test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined
>>> int('five')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'five'
>>> list(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> 8 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Possiamo vedere che ogni eccezione ha un tipo (es. NameError) e un messaggio che descrive l'errore (es. name 'test' is not defined). Questi tipi sono organizzati in una gerarchia, che include eccezioni più o meno specifiche che vengono utilizzate per situazioni diverse. Per esempio, l'eccezione ZeroDivisionError è un caso particolare di ArithmeticError, che è un sotto-tipo di Exception, che a sua volta è un sotto-tipo di BaseException.

Possiamo anche notare che alcuni errori (i SyntaxError) non hanno un traceback, mentre altri sì. Questo è dovuto al fatto che i SyntaxError vengono riportati quando il codice che abbiamo scritto non è valido e avvengono in fase di parsing, quindi prima che l'interprete possa eseguire il codice. Gli altri errori invece includono anche un traceback che riporta informazioni sulla sequenza di operazioni che hanno portato all'errore durante l'esecuzione del programma. Ad esempio:

>>> def a(x, y):
...     return x / y
...
>>> def b(x, y):
...     return a(x, y)
...
>>> def c(x, y):
...     return b(x, y)
...
>>> c(8, 2)
4.0
>>> c(8, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in c
  File "<stdin>", line 2, in b
  File "<stdin>", line 2, in a
ZeroDivisionError: division by zero

In questo caso la funzione c chiama la funzione b, e la funzione b chiama la funzione a. Se a restituisce un errore, l'errore si propaga prima a b, poi a c e infine, visto che nessuno ha gestito l'eccezione, il programma mostra un errore e termina. Il traceback mostra questa sequenza di chiamate in ordine cronologico (la chiamata più recente si trova alla fine).

Gestire le eccezioni

Abbiamo visto che diverse operazioni in Python possono restituire un'eccezione:

>>> n = int('five')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'five'

Python ci da modo di catturare queste eccezioni e di gestirle, mediante il try/except:

>>> try:
...     n = int('five')
... except ValueError:
...     print('Invalid number!')
...
Invalid number!

Possiamo notare che:

  • La keyword try è seguita dai due punti (:) e da un blocco indentato di codice
  • Il blocco di codice contiene il codice che potrebbe generare un'eccezione
  • Dopo il primo blocco di codice, indentato allo stesso livello del try abbiamo l'except, seguito dal nome dell'eccezione che vogliamo catturare e dai due punti (:)
  • Indentato sotto l'except abbiamo un blocco di codice usato per gestire l'eccezione

Il funzionamento è semplice: se il codice nel blocco del try genera un'eccezione del tipo specificato dall'except, allora il blocco dell'except viene eseguito per gestirla. Se il codice nel blocco del try non genera un'eccezione, o l'eccezione generata non è del tipo specificato dall'except, allora l'eccezione si propaga e il blocco dell'except viene ignorato.

È importante notare che l'except cattura tutte le eccezioni del tipo specificato, ma anche tutti i suoi sotto-tipi, quindi se specifichiamo un ArithmeticError nell'except, l'except catturerà ArithmeticError, ma anche i suoi tre sotto-tipi ZeroDivisionError, OverflowError, e FloatingPointError:

>>> try:
...     n = 5 / 0
... except ArithmeticError:
...     print('Invalid operation!')
...
Invalid operation!

Questa forma di try/except è quella più semplice, in realtà il try/except è un costrutto molto flessibile e potente. Vediamo alcuni altri esempi.

>>> try:
...     n = 5 / 0
... except ZeroDivisionError as err:
...     print('Invalid operation ({})!'.format(err))
...
Invalid operation (division by zero)!

È possibile aggiungere dopo l'except, la keyword as seguita dal nome di una variabile (ad esempio err). Questo rende accessibile l'errore all'interno del blocco di codice dell'except permettendoci, tra le altre cose, di stamparlo.

>>> def try_except_else_test(x):
...     try:
...         n = int(x)  # prova a convertire x in intero
...     except ValueError:
...         # eseguito in caso di ValueError
...         print('Invalid number!')
...     else:
...         # eseguito se non ci sono errori
...         print('Valid number!')
...
>>> try_except_else_test('five')  # numero non valido: esegue l'except
Invalid number!
>>> try_except_else_test('5')  # numero valido: esegue l'else
Valid number!

È possibile aggiungere un else dopo l'except che viene chiamato se il codice nel blocco del try viene eseguito senza che ritorni nessuna eccezione.

>>> def try_except_except_test(x):
...     try:
...         n = int(x)  # prova a convertire x in intero
...     except ValueError:
...         # eseguito in caso di ValueError
...         print('Invalid number!')
...     except TypeError:
...         # eseguito in caso di TypeError
...         print('Invalid type!')
...     else:
...         # eseguito se non ci sono errori
...         print('Valid number!')
...
>>> try_except_except_test('five')  # tipo valido ma valore invalido: esegue il primo except
Invalid number!
>>> try_except_except_test([1, 2, 3])  # tipo invalido: esegue il secondo except
Invalid type!
>>> try_except_except_test('5')  # numero valido: esegue l'else
Valid number!

È possibile aggiungere più di un except in modo da gestire eccezioni diverse in modo diverso. Quando il codice nel blocco del try genera un'eccezione, Python eseguire il primo except che specifica un'eccezione del tipo corretto.

>>> f = open('test.txt', 'w')  # apre un file in scrittura
>>> try:
...     f.read()  # prova a leggere e fallisce
... finally:
...     f.close()  # il file viene chiuso nonostante l'errore riportato
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
io.UnsupportedOperation: not readable
>>> f.closed  # verifichiamo che il file sia chiuso
True
>>> f = open('test.txt')  # riapriamo lo stesso file in lettura (default)
>>> try:
...     f.read()  # proviamo a leggere (ora funziona senza errori)
... finally:
...     f.close()  # il file viene chiuso
...
''
>>> f.closed  # verifichiamo che il file sia chiuso
True

Se vogliamo specificare una o più operazioni che vanno eseguite sia in caso di errore che in caso di successo, possiamo aggiungere un finally seguito dai due punti e da un blocco di codice indentato che viene sempre eseguito. È anche possibile aggiungere un finally dopo else/except. (Nella prossima lezione vedremo in modo più approfondito i file.)

Riportare eccezioni

Oltre alla keyword return, usata per restituire un risultato, in Python esiste anche la keyword raise usata per riportare un'eccezione.

>>> def div(num, den):
...     if den == 0:
...         # se il denominatore è 0 riporta un'eccezione
...         raise ZeroDivisionError('Impossibile dividere per 0')
...     return num / den
...
>>> div(8, 2)
4.0
>>> div(8, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in div
ZeroDivisionError: Impossibile dividere per 0

Per riportare un'eccezione, bisogna prima di tutto creare un oggetto Exception (o un sotto-tipo di Exception). Per farlo chiamiamo l'eccezione passando un messaggio d'errore come stringa (ad esempio ZeroDivisionError('Impossibile dividere per 0')). Una volta che abbiamo creato questo oggetto, possiamo usare la keyword raise per riportare l'eccezione. Quando il raise viene eseguito, il flusso del programma viene interrotto, e l'eccezione viene riportata al chiamante. Se nessuno cattura l'eccezione usando un try/except, il programma termina con un messaggio d'errore.

Il raise viene generalmente usato per riportare una nuova eccezione, ma si può anche usare all'interno di un try/except per consentire all'eccezione originale di propagarsi:

>>> try:
...     5 / 0
... except ZeroDivisionError as err:
...     # stampa informazioni sull'errore
...     print('* Logged exception:', err)
...     print('* Re-raising exception.')
...     raise  # lascia che l'eccezione originale si propaghi
...
* Logged exception: division by zero
* Re-raising exception.
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

In questo caso è possibile usare semplicemente raise all'interno dell'except senza specificare nessuna eccezione. Questo ci consente di intercettare momentaneamente un'eccezione, eseguire delle operazioni (ad esempio salvare l'errore su un file), e lasciare che l'eccezione originale continui a propagarsi.

Infine è possibile concatenare eccezioni:

>>> try:
...     5 / 0
... except ZeroDivisionError:
...     # cattura l'errore originale e ne riporta uno nuovo
...     raise ValueError('Invalid denominator value!')
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
ValueError: Invalid denominator value!

Questo esempio ci mostra come sia possibile creare e riportare una nuova eccezione all'interno di un except. Python mostrerà sia il traceback dell'eccezione originale sia quello della nuova eccezione.

È anche possibile creare nuovi tipi di eccezioni creando delle sub-classi (dei sotto-tipi) di Exception, ma per poterlo fare bisogna prima capire il funzionamento delle classi, che verranno trattate in una lezione futura.

Ti consigliamo anche