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 b b a a b c

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 :
  • Il blocco di codice contiene il codice che potrebbe generare un'eccezione
  • Dopo il primo blocco di codice, indentato allo stesso livello del try except :
  • Indentato sotto l'except

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 try/except

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

È possibile aggiungere dopo l'except as err except

>>> 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 except try

>>> 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 try except

>>> 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 finally else/except

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 except

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