Skip to content

5. Le eccezioni

Passiamo ora alle eccezioni.

Image

5.1. script [exceptions_01]

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

# si genera intenzionalmente un errore
x = 4 / 0

Si genera intenzionalmente un errore per vedere cosa succede (exceptions-01.py):

1
2
3
4
5
6
7
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero

Process finished with exit code 1

La riga 4 ci fornisce:

  • il tipo dell'eccezione: ZeroDivisionError;
  • il messaggio di errore associato: division by zero. È in inglese. È qualcosa che potremmo voler modificare.

Una regola fondamentale è che bisogna fare di tutto per evitare le eccezioni generate dall’interprete Python. Dobbiamo gestire gli errori da soli.

La sintassi per la gestione delle eccezioni è la seguente:


try:
    actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
    actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
    actions de gestion de l'exception [ex]
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 (errore). In questo caso, l'esecuzione prosegue con le azioni di una delle clausole **except:

  • riga 3: se l’eccezione [ex] che si è verificata è di un tipo appartenente alla tupla (Ex1, Ex2…) o derivato da uno di essi, allora vengono eseguite le azioni della riga 4;
  • riga 5: se l’eccezione non è stata intercettata dalla riga 3 e esiste un’altra clausola [except], allora si svolge lo stesso processo. E così via…;
  • possono esserci tutte le clausole [except] necessarie per gestire i diversi tipi di eccezione che possono verificarsi nel [try];
  • se l’eccezione non è stata gestita da nessuna delle clausole [except], allora verrà segnalata al codice chiamante. Se quest’ultimo si trova a sua volta all’interno di una struttura try/except, l’eccezione viene nuovamente gestita; in caso contrario, continua a risalire la catena dei metodi chiamati. In ultima istanza, l'eccezione arriva all'interprete Python. Quest'ultimo interrompe quindi l'esecuzione del programma 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 possono risalire dai metodi chiamati;
  • riga 7: la clausola [finally] viene sempre eseguita, indipendentemente dal fatto che si sia verificata un’eccezione (seguito dell’except) o meno (seguito del try). Ciò vale anche se si è verificata un’eccezione che non è stata intercettata. In questo caso, la clausola [finally] verrà eseguita prima che l’eccezione venga propagata al codice chiamante;

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


except MyException as exception:

[exception] è l’eccezione che si è verificata. [exception.args] rappresenta la tupla dei parametri dell’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 BaseException. I parametri passati al costruttore della classe saranno disponibili nella clausola except delle strutture di intercettazione delle eccezioni con la sintassi **[ex.args] se **[ex] è l'eccezione intercettata dalla clausola **[except]**.

Questi concetti sono illustrati dal seguente script.

5.2. script [exceptions_02]

Il seguente script gestisce esplicitamente gli errori:


# gestione delle eccezioni
essai = 0

# si genera un errore e lo si gestisce
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}")
# il valore di x non è cambiato
print(f"x={x}")

# si ricomincia
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    # si intercetta l'eccezione più generica
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}")

# è possibile intercettare diversi tipi di eccezioni
# l'esecuzione si interrompe sul primo [except] in grado di gestire l'eccezione
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # questa eccezione non si verifica qui
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    # viene intercettata l'eccezione più generica
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    # si intercetta un tipo specifico
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

# si ricomincia cambiando l'ordine 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # questa eccezione non si verifica qui
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    # si intercetta un tipo specifico
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    # si intercetta l'eccezione più generica
    print(f"essai n° {essai} : (Exception) {erreur}")

# una clausola "except" senza argomenti
essai += 1
try:
    x = 4 / 0
except:
    # non ci interessa la natura dell'eccezione
    print(f"essai n° {essai} : il y a eu un problème")

# un altro tipo di eccezione
essai += 1
try:
    # x non può essere convertito in un numero intero
    x = int("x")
except ValueError as erreur:
    # "errore" è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}")

# un'eccezione trasporta informazioni in una tupla accessibile al programma
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# È possibile generare eccezioni
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


# è possibile creare le proprie eccezioni
# devono derivare dalla classe [BaseException]
class MyError(BaseException):
    pass


# si lancia l'eccezione MyError
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# viene generata l'eccezione MyError con un messaggio di errore
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    # l'errore è l'eccezione intercettata
    print(f"essai n° {essai} : {erreur.args[0]}")

# la clausola finally viene sempre eseguita
# indipendentemente dal fatto che si verifichi o meno un'eccezione
essai += 1
x = None
try:
    x = 1
except:
    # eccezione
    print(f"essai n° {essai} : exception")
finally:
    # eseguito in ogni caso
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    # eccezione
    print(f"essai n° {essai} : exception")
finally:
    # eseguito in ogni caso
    print(f"essai n° {essai} : finally x={x}")

# non è necessario inserire una clausola [except]
essai += 1
try:
    # si genera un errore
    x = 4 / 0
finally:
    # eseguito in ogni caso
    print(f"essai n° {essai} : finally x={x}")

Note:

  • righe 4-12: si gestisce una divisione per zero;
  • riga 8: si intercetta l'eccezione esatta che si verifica e la si visualizza;
  • riga 12: a causa dell'eccezione verificatasi, x non ha ricevuto alcun valore alla riga 7 e quindi non ha modificato il proprio valore;
  • righe 14-21: si ripete la stessa operazione, ma intercettando un'eccezione di livello superiore di tipo BaseException. Poiché l’eccezione ZeroDivisionError deriva dalla classe BaseException, la clausola except la interromperà;
  • righe 23-36: si inseriscono diverse clausole except per gestire diversi tipi di eccezione. Verrà eseguita una sola clausola except oppure nessuna se l’eccezione non soddisfa nessuna clausola except;
  • righe 38-50: si ripete la stessa operazione modificando l’ordine delle clausole [except] per illustrarne il funzionamento;
  • righe 52-58: la clausola except può non avere alcun argomento. In questo caso, interrompe tutte le eccezioni;
  • righe 60-67: introducono l'eccezione ValueError;
  • righe 69-75: si recuperano le informazioni trasportate dall'eccezione;
  • righe 77-83: illustrano come generare (raise) un'eccezione;
  • righe 86-106: illustrano l'utilizzo di una classe di eccezione proprietaria MyError. La classe MyError si limita a derivare dalla classe base BaseException. Non aggiunge nulla alla sua classe base. Tuttavia, ora può essere nominata esplicitamente nelle clausole except;
  • righe 108-130: illustrano l'uso della clausola finally;
  • righe 132-139: queste righe mostrano che la clausola [except] non è obbligatoria;

I risultati sullo schermo sono i seguenti:

C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None

Process finished with exit code 1

5.3. script [exceptions_03]

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


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


# tre funzioni
def f1(x: int) -> int:
    # le eccezioni non vengono gestite: vengono segnalate automaticamente
    return f2(x)


def f2(y: int) -> int:
    # le eccezioni non vengono gestite: vengono segnalate automaticamente
    return f3(y)


def f3(z: int) -> int:
    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 erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


# altre tre funzioni che arricchiscono le eccezioni che vengono propagate
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        # si arricchisce l'eccezione e poi la si rilancia
        raise MyError("exception dans f4", erreur)


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


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


# ---------- main
try:
    print(f4(4))
except MyError as erreur:
    # visualizzazione dell'eccezione
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    # è possibile risalire la pila delle eccezioni
    err = erreur
    # viene visualizzato il messaggio di errore
    print(err.args[0])
    # un'eccezione è incapsulata?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        # modifica dell'eccezione
        err = err.args[1]
        # il primo argomento è il messaggio di errore
        print(err.args[0])

Note:

  • righe 25-32, nella chiamata main --> f1 --> f2 --> f3 (riga 30), l'eccezione MyError generata da f3 verrà propagata fino a main. Verrà quindi gestita dalla clausola except della riga 31;
  • righe 61-75: nella chiamata main --> f4 --> f5 --> f6 (riga 62), l’eccezione MyError generata da f6 risalirà fino a main. Verrà quindi gestita dalla clausola except della riga 63. Questa volta, nel suo percorso risalendo la catena delle funzioni chiamanti, l’eccezione MyError che risale è a sua volta incapsulata in un’altra eccezione;
  • righe 66-75: mostrano come risalire lo stack delle eccezioni;
  • riga 71: la funzione [isinstance(instance, Classe)] restituisce True se l’oggetto [instance] è di tipo [Classe] o derivato. In questo caso abbiamo utilizzato l’eccezione di livello più alto [BaseException], il che ci garantisce di recuperare tutte le eccezioni;

I risultati visualizzati sullo schermo sono i seguenti:

1
2
3
4
5
6
7
8
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6

Process finished with exit code 0