Subsections


8. Errori ed eccezioni

Fino ad ora i messaggi di errore sono stati solo nominati, ma se avete provato a eseguire gli esempi ne avrete visto probabilmente qualcuno. Si possono distinguere (come minimo ) due tipi di errori: gli errori di sintassi e le eccezioni.


8.1 Errori di sintassi

Gli errori di sintassi, noti anche come errori di parsing, sono forse il tipo più comune di messaggio di errore che si riceve mentre si sta ancora imparando Python:

>>> while True print 'Ciao mondo'
  File "<stdin>", line 1, in ?
    while True print 'Ciao mondo'
                   ^
SyntaxError: invalid syntax

L'analizzatore sintattico (`parser') riporta la riga incriminata e mostra una piccola `freccia' che punta al primissimo punto in cui è stato rilevato l'errore nella riga incriminata . L'errore è causato dal token che precede la freccia (o quantomeno rilevato presso di esso); nell'esempio l'errore è rilevato alla parola chiave print, dato che mancano i duepunti (":") prima di essa. Vengono stampati il nome del file e il numero di riga, in modo che si sappia dove andare a guardare, nel caso l'input provenga da uno script.


8.2 Le eccezioni

Anche se un'istruzione, o un'espressione, è sintatticamente corretta, può causare un errore quando si tenta di eseguirla. Gli errori rilevati durante l'esecuzione sono chiamati eccezioni e non sono incondizionatamente fatali: si imparerà presto come gestirli nei programmi Python. La maggior parte delle eccezioni comunque non sono gestite dai programmi e causano dei messaggi di errore, come i seguenti:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

L'ultima riga del messaggio di errore indica cos'è successo. Le eccezioni sono di diversi tipi, ed il loro tipo viene stampato come parte del messaggio: i tipi che compaiono nell'esempio sono ZeroDivisionError, NameError e TypeError. La stringa stampa quale tipo d'eccezione è occorsa ed il nome dell'eccezione stessa. Ciò è vero per tutte le eccezioni built-in, ma non è necessario che lo sia per le eccezioni definite dall'utente (malgrado si tratti di una convenzione utile). I nomi delle eccezioni standard sono identificatori built-in, non parole chiave riservate.

Il resto della riga è un dettaglio la cui interpretazione dipende dal tipo d'eccezione; anche il suo significato dipende dal tipo d'eccezione.

La parte antecedente del messaggio di errore mostra il contesto in cui è avvenuta l'eccezione, nella forma di una traccia dello stack (``stack backtrace''). In generale contiene una traccia dello stack che riporta righe di codice sorgente; in ogni caso non mostrerà righe lette dallo standard input.

La La libreria Python di riferimento elenca le eccezioni built-in ed i loro significati.


8.3 Gestire le eccezioni

È possibile scrivere programmi che gestiscono determinate eccezioni. Si esamini il seguente esempio, che richiede un input fino a quando non viene introdotto un intero valido, ma permette all'utente di interrompere il programma (usando Control-C o qualunque cosa sia equivalente per il sistema operativo); si noti che un'interruzione generata dall'utente viene segnalata sollevando un'eccezione KeyboardInterrupt.

>>> while True:
...     try:
...         x = int(raw_input("Introduci un numero: "))
...         break
...     except ValueError:
...         print "Oops! Non era un numero valido. Ritenta..."
...

L'istruzione try funziona nel modo seguente.

Un'istruzione try può avere più di una clausola except, per specificare gestori di differenti eccezioni. Al più verrà eseguito un solo gestore. I gestori si occupano solo delle eccezioni che ricorrono nella clausola try corrispondente, non in altri gestori della stessa istruzione try. Una clausola except può nominare più di un'eccezione sotto forma di tupla, per esempio:

... except (RuntimeError, TypeError, NameError):
...     pass

Nell'ultima clausola except si può tralasciare il nome (o i nomi) dell'eccezione, affinché serva da jolly. Si presti estrema cautela, dato che in questo modo è facile mascherare un errore di programmazione vero e proprio! Può anche servire per stampare un messaggio di errore e quindi risollevare l'eccezione (permettendo pure a un chiamante di gestire l'eccezione):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, (errno, strerror):
    print "Errore I/O (%s): %s" % (errno, strerror)
except ValueError:
    print "Non si può convertire il dato in un intero."
except:
    print "Errore inatteso:", sys.exc_info()[0]
    raise

L'istruzione try ... except ha una clausola else facoltativa, che, ove presente, deve seguire tutte le clausole except. È utile per posizionarvi del codice che debba essere eseguito in caso la clausola try non sollevi un'eccezione. Per esempio:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'non posso aprire', arg
    else:
        print arg, 'è di', len(f.readlines()), 'righe'
        f.close()

L'uso della clausola else è preferibile all'aggiunta di codice supplementare alla try poiché evita la cattura accidentale di un'eccezione che non è stata rilevata dal codice protetto dall'istruzione try ... except.

Quando interviene un'eccezione, essa può avere un valore associato, conosciuto anche come argomento dell'eccezione. La presenza e il tipo dell'argomento dipendono dal tipo di eccezione.

La clausola except può specificare una variabile dopo il nome dell'eccezione (o una lista di nomi). La variabile è limitata al caso di un'eccezione con argomenti immagazzinati in instance.args. Per convenienza, l'eccezione definisce __getitem__ e __str__ in modo da poter accedere agli argomenti o stamparli direttamente senza dover riferirsi ad .args.

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception, inst:
...    print type(inst)    # l'istanza dell'eccezione
...    print inst.args     # argomenti immagazzinati in .args
...    print inst          # __str__ consente di stampare gli argomenti
...    x, y = inst         # __getitem__ consente di scomporre gli argomenti
...    print 'x =', x
...    print 'y =', y
...
<type 'instance'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Se un'eccezione ha un argomento, questo viene stampato come ultima parte (`dettaglio') del messaggio per le eccezioni non gestite.

I gestori delle eccezioni non si occupano solo delle eccezioni che vengono sollevate direttamente nella clausola try, ma anche di quelle che ricorrono dall'interno di funzioni chiamate (anche indirettamente) nella clausola try. Per esempio:

>>> def this_fails():
...     x = 1/0
... 
>>> try:
...     this_fails()
... except ZeroDivisionError, detail:
...     print 'Gestione dell'errore a runtime:', detail
... 
Gestione dell'errore a runtime: integer division or modulo


8.4 Sollevare eccezioni

L'istruzione raise permette al programmatore di forzare una specifica eccezione. Per esempio:

>>> raise NameError, 'HiThere'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

Il primo argomento di raise menziona l'eccezione da sollevare. Il secondo argomento, facoltativo, specifica l'argomento dell'eccezione.

Se si ha bisogno di sollevare un'eccezione, ma non si vuole gestirla, si può usare una semplice forma dell'istruzione raise che consente di risollevare l'eccezione.

>>> try:
...     raise NameError, 'HiThere'
... except NameError:
...     print 'Un'eccezione svetta qui intorno!'
...     raise
...
Un'eccezione svetta qui intorno!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere


8.5 Eccezioni definite dall'utente

I programmi possono dare un nome a delle proprie eccezioni creando una nuova classe di eccezioni. Le eccezioni dovrebbero, di regola, derivare dalla classe Exception, direttamente o indirettamente. Per esempio:

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
... 
>>> try:
...     raise MyError(2*2)
... except MyError, e:
...     print 'Occorsa una mia eccezione, valore:', e.value
... 
Occorsa una mia eccezione, valore: 4
>>> raise MyError, 'oops!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

Una classe di eccezioni si può definire come una qualsiasi altra classe, solitamente piuttosto semplice, in grado di offrire un certo numero di attributi che permettono l'estrazione delle informazioni sull'errore tramite la gestione dell'eccezione. Nel realizzare un modulo capace di sollevare eccezioni per diversi, distinti errori, una pratica comune è creare una classe base per le eccezioni definite nel modulo e una sottoclasse che per ogni specifica condizione d'errore:

class Error(Exception):
    """Classe base per le eccezioni definite in questo modulo."""
    pass

class InputError(Error):
    """Eccezione sollevata per errori di immissione.

    Attributi:
        espressione -- dati in ingresso che contengono errori
        messaggio -- spiegazione dell'errore
    """

    def __init__(self, espressione, messaggio):
        self.espressione = espressione
        self.messaggio = messaggio

class TransitionError(Error):
    """Solleva l'eccezione quando in un passaggio si tenta di
    specificare un'operazione non consentita.

    Attributi:
        precedente -- stato all'inizio dell'operazione
        successivo -- cerca il nuovo stato
        messaggio -- spiegazione del perché la specifica operazione non
                     è consentita
    """

    def __init__(self, precedente, successivo, messaggio):
        self.precedente = precedente
        self.successivo = successivo
        self.messaggio = messaggio

Molte eccezioni vengono definite con nomi che terminano con ``Error,'' analogamente ai nomi delle eccezioni convenzionali.

Molti moduli standard usano questa tecnica per le proprie eccezioni per riportare errori che possono verificarsi nelle funzioni che definiscono. Ulteriori informazioni sulle classi sono presenti nel capitolo 9, ``Classi.''


8.6 Definire azioni di chiusura

L'istruzione try ha un'altra clausola facoltativa, che serve a definire azioni di chiusura (NdT: `clean-up') che devono essere eseguite in tutti i casi. Per esempio:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Addio, mondo!'
... 
Addio, mondo!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

Una clausola finally viene eseguita comunque, che l'eccezione sia stata sollevata nella clausola try o meno. Nel caso sia stata sollevata un'eccezione, questa viene risollevata dopo che la clausola finally è stata eseguita. La clausola finally viene eseguita anche ``quando si esce'' da un'istruzione try per mezzo di un'istruzione break o return.

Il codice nella clausola finally è utile per abbandonare risorse esterne (come nel caso di file o connessioni di rete), senza curarsi del fatto che si sia usata con successo o meno la risorsa esterna.

Un'istruzione try deve avere una o più clausole except o una clausola finally, ma non entrambe.

Vedete Circa questo documento... per informazioni su modifiche e suggerimenti.