Skip to content

6. Le eccezioni

Passiamo ora alle eccezioni.

  

Programma (exceptions_01)

Il primo script illustra la necessità di gestire le eccezioni.


# -*- coding=utf-8 -*-

# si genera un errore
x=4/0

Si genera intenzionalmente un errore per osservare le informazioni prodotte dall’interprete. Il risultato visualizzato sullo schermo è il seguente:

1
2
3
4
Traceback (most recent call last):
  File "D:\data\istia-1112\python\tutoriel\exceptions_01.py", line 4, in <module>
    x=4/0
ZeroDivisionError: integer division or modulo by zero

La riga 4 ci fornisce:

  • il tipo dell'eccezione: ZeroDivisionError;
  • il messaggio di errore associato: «integer division or modulo by zero». È in inglese. È un aspetto che si potrebbe voler modificare.

La regola fondamentale nella programmazione è che bisogna fare di tutto per evitare arresti anomali "imprevisti" come quello sopra riportato. Anche in caso di errore, il programma deve terminare in modo corretto, fornendo informazioni sull'errore che si è verificato.

La sintassi per la gestione delle eccezioni è la seguente:


try:
    actions susceptibles de lancer une exception
except [classe d'exception, ...]:
    actions de gestion de l'exception
finally:
   actions toujours exécutées qu'il y ait exception ou non

Nel blocco **try**, l'esecuzione delle azioni si interrompe non appena si verifica un'eccezione. In tal caso, l'esecuzione prosegue con le azioni della clausola **except**.

La clausola


except [MyException, ...]:

intercetta le eccezioni di tipo MyException o derivate. Quando in un blocco try si verifica un'eccezione, l'interprete esamina le clausole except associate al blocco try nell'ordine in cui sono state scritte. Si ferma sulla prima clausola except che consente di gestire l'eccezione verificatasi. Se non ne trova nessuna, l’eccezione risale al metodo chiamante. Se quest’ultimo contiene un blocco try/except, l’eccezione viene nuovamente gestita; in caso contrario, continua a risalire la catena dei metodi chiamati. In ultima istanza, arriva all’interprete Python, il quale interrompe il programma in esecuzione e visualizza un messaggio di errore del tipo mostrato nell’esempio precedente. La regola è quindi che il programma principale deve intercettare tutte le eccezioni che potrebbero risalire dai metodi chiamati.

Un’eccezione trasporta con sé informazioni sull’errore che si è verificato. È possibile ottenerle con la seguente sintassi:


except MyException as informations:

informazioni è una tupla che contiene le informazioni relative all’eccezione.

La sintassi


except MyException, erreur:

assegna alla variabile erreur il messaggio di errore relativo all'eccezione.

Per generare un'eccezione, si utilizza la sintassi


raise MyException(param1, param2, ...)

dove, nella maggior parte dei casi, MyException è una classe derivata dalla classe Exception. I parametri passati al costruttore della classe saranno disponibili nella clausola except delle strutture di intercettazione delle eccezioni.

Questi concetti sono illustrati dal seguente script.


Programma (exceptions_02)

Il seguente script gestisce esplicitamente gli errori:


# -*- coding=utf-8 -*-

i=0
# si genera un errore e lo si gestisce
x=2
try:
    x=4/0
except ZeroDivisionError, erreur:
    print ("%s : %s ") % (i, erreur)
# il valore di x non è cambiato
print "x=%s" % (x)

# si ricomincia
i+=1
try:
    x=4/0
except Exception, erreur:
    # si intercetta l'eccezione più generica
    print ("%s : %s ") % (i, erreur)

# è possibile intercettare diversi tipi di eccezioni 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # questa eccezione non si verifica qui
    print ("%s : %s ") % (i, erreur)
except Exception, erreur:
    # si intercetta l'eccezione più generica
    print ("%s : (Exception) %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # si intercetta un tipo specifico
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)

# si ricomincia cambiando l'ordine 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # questa eccezione non si verifica qui
    print ("%s : %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # si intercetta un tipo specifico
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)
except Exception, erreur:
    # si intercetta l'eccezione più generica
    print ("%s : (Exception) %s ") % (i, erreur)

# una clausola except senza argomenti
i+=1
try:
    x=4/0
except:
    # non ci interessa né la natura dell'eccezione né il messaggio di errore
    print ("%s : il y a eu un probleme ") % (i)

# un altro tipo di eccezione
i+=1
try:
    x=int("x")
except ValueError, erreur:
    print ("%s : %s ") % (i, erreur)

# un'eccezione trasporta informazioni in una tupla accessibile al programma
i+=1
try:
    x=int("x")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# È possibile generare delle eccezioni
i+=1
try:
    raise ValueError("param1","param2","param3")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# È possibile creare le proprie eccezioni
class MyError(Exception):
    pass

# si genera l'eccezione MyError
i+=1
try:
    raise MyError("info1","info2", "info3")
except MyError as infos:
    print ("%s : %s ") % (i, infos)

# si genera l'eccezione MyError
i+=1
try:
    raise MyError("mon msg d'erreur")
except MyError, erreur:
    print ("%s : %s ") % (i, erreur)

# è possibile lanciare qualsiasi tipo di oggetto
class Objet:
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

i+=1
try:
    raise Objet("pb...")
except Objet as erreur:
    print "%s : %s" % (i, erreur)

# la clausola finally viene sempre eseguita
# indipendentemente dal fatto che si verifichi o meno un'eccezione
i+=1
x=None
try:
    x=1
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

i+=1
x=None
try:
    x=2/0
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

Note:

  • righe 6-9: si gestisce una divisione per zero;
  • riga 8: si intercetta l'eccezione esatta che si verifica;
  • riga 11: a causa dell'eccezione verificatasi, x non ha ricevuto alcun valore e quindi non ha modificato il proprio valore;
  • righe 15-19: si ripete la stessa operazione, ma intercettando un'eccezione di livello superiore di tipo Exception. Poiché l’eccezione ZeroDivisionError deriva dalla classe Exception, la clausola except la interromperà;
  • righe 23-33: si inseriscono diverse clausole except per gestire diversi tipi di eccezione. Verrà eseguita una sola clausola except oppure nessuna se l’eccezione non soddisfa alcuna clausola except;
  • righe 51-55: la clausola except può non avere alcun argomento. In tal caso, interrompe tutte le eccezioni;
  • righe 59-62: introducono l'eccezione ValueError;
  • righe 66-69: si recuperano le informazioni trasportate dall'eccezione;
  • righe 73-76: illustrano come generare un'eccezione;
  • righe 79-84: illustrano l'utilizzo di una classe di eccezione proprietaria MyError;
  • righe 79-80: la classe MyError si limita a derivare dalla classe base Exception. Non aggiunge nulla alla sua classe base. Tuttavia, ora può essere nominata esplicitamente nelle clausole except;
  • righe 97-107: illustrano che in Python è possibile lanciare qualsiasi tipo di oggetto e non solo oggetti derivati dalla classe Exception;
  • righe 109-127: illustrano l'uso della clausola finally.

I risultati visualizzati sullo schermo sono i seguenti:

0 : integer division or modulo by zero
x=2
1 : integer division or modulo by zero
2 : (Exception) integer division or modulo by zero
3 : (ZeroDivisionError) integer division or modulo by zero
4 : il y a eu un probleme
5 : invalid literal for int() with base 10: 'x'
6 : invalid literal for int() with base 10: 'x'
7 : ('param1', 'param2', 'param3')
8 : ('info1', 'info2', 'info3')
9 : mon msg d'erreur
10 : pb...
11 : finally x=1
12 : exception
12 : finally x=None

Programma (exceptions_03)

Questo nuovo script illustra la propagazione delle eccezioni nella catena dei metodi chiamanti:


# -*- coding=utf-8 -*-

# un'eccezione proprietaria
class MyError(Exception):
    pass

# tre metodi
def f1(x):
    # le eccezioni non vengono gestite: vengono propagate automaticamente
    return f2(x)

def f2(y):
    # non si gestiscono le eccezioni: vengono segnalate automaticamente
    return f3(y)

def f3(z):
    if (z % 2) == 0:
        # se z è pari, viene generata un'eccezione 
        raise MyError("exception dans f3")
    else:
        return 2*z

#---------- main

# le eccezioni risalgono la catena dei metodi chiamati
# finché un metodo non le intercetta. In questo caso sarà main
try:
    print f1(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

# un metodo può arricchire le eccezioni che inoltra
def f4(x):
    try:
        return f5(x)
    except MyError as infos:
        # si arricchisce l'eccezione e poi la si rilancia
        raise MyError(infos,"exception dans f4")

def f5(y):
    try:
        return f6(y)
    except MyError as infos:
        # si arricchisce l'eccezione e poi la si rilancia
        raise MyError(infos, "exception dans f5")

def f6(z):
    if (z % 2) == 0:
        # si genera un'eccezione
        raise MyError("exception dans f6")
    else:
        return 2*z

#---------- main
try:
    print f4(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

Note:

  • righe 27-30, nella chiamata main --> f1 --> f2 --> f3 (riga 28), l'eccezione MyError generata da f3 verrà propagata fino a main. Verrà quindi gestita dalla clausola except della riga 29;
  • righe 55-58: nella chiamata main --> f4 --> f5 --> f6 (riga 56), l’eccezione MyError generata da f6 risalirà fino a main. Verrà quindi gestita dalla clausola except della riga 29. Questa volta, durante la risalita lungo la catena dei metodi chiamanti, l'eccezione MyError viene arricchita con le informazioni aggiunte da ciascun metodo risalito.

I risultati visualizzati sullo schermo sono i seguenti:

type :<class '__main__.MyError'>, arguments : exception dans f3
type :<class '__main__.MyError'>, arguments : (MyError(MyError('exception dans f6',), 'exception dans f5'), 'exception dans f4')