Skip to content

5. Eccezioni

Passeremo ora alle eccezioni.

Image

5.1. script [exceptions_01]

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

# on provoque volontairement une erreur
x = 4 / 0

Provociamo 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 dà:

  • il tipo di eccezione: ZeroDivisionError;
  • il messaggio di errore associato: divisione per zero. È in inglese. Potresti volerlo cambiare.

Una regola fondamentale è che dobbiamo fare tutto il possibile 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] verificatasi è di un tipo appartenente alla tupla (Ex1, Ex2…) o derivata da uno di essi, allora vengono eseguite le azioni alla riga 4;
  • riga 5: se l'eccezione non è stata intercettata dalla riga 3 ed esiste un'altra clausola [except], si verifica lo stesso processo. E così via…;
  • possono esserci tutte le clausole [except] necessarie per gestire i diversi tipi di eccezioni che possono verificarsi all'interno del blocco [try];
  • se l'eccezione non è stata gestita da nessuna delle clausole [except], allora si propagherà a ritroso verso il codice chiamante. Se il codice chiamante si trova a sua volta all'interno di una struttura try/except, l'eccezione viene gestita nuovamente; altrimenti, continua a propagarsi verso l'alto lungo la catena dei metodi chiamati. Alla fine, raggiunge l'interprete Python. L'interprete quindi termina 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 possono propagarsi dai metodi chiamati;
  • riga 7: la clausola [finally] viene sempre eseguita, indipendentemente dal fatto che si sia verificata un'eccezione (dopo l'except) o meno (dopo il 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 si propaghi al codice chiamante;

Un'eccezione contiene informazioni sull'errore verificatosi. Queste informazioni possono essere ottenute utilizzando 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, utilizzare la sintassi


raise MyException(param1, param2…)

dove MyException è molto spesso una classe derivata dalla classe BaseException. I parametri passati al costruttore della classe saranno disponibili per la clausola except delle strutture di gestione delle eccezioni utilizzando 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:

#  exception handling
essai = 0

#  cause an error and manage it
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")
#  the value of x has not changed
print(f"x={x}")

#  here we go again
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    #  we intercept the most general exception
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")

#  different types of exceptions can be intercepted
#  execution stops on the 1st [except] able to handle the exception
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    #  this exception does not occur here
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    #  we intercept the most general exception
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    #  we intercept a specific type
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

#  start again, changing the order 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    #  this exception does not occur here
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    #  we intercept a specific type
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    #  we intercept the most general exception
    print(f"essai n° {essai} : (Exception) {erreur}")

#  an except clause with no arguments
essai += 1
try:
    x = 4 / 0
except:
    #  we're not interested in the nature of the exception
    print(f"essai n° {essai} : il y a eu un problème")

#  another type of exception
essai += 1
try:
    #  x cannot be converted to an integer
    x = int("x")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")

#  an exception transports information in a tuple accessible to the program
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

#  exceptions can be thrown
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


#  you can create your own exceptions
#  they must derive from class [BaseException]
class MyError(BaseException):
    pass


#  throw MyError exception
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

#  throw MyError exception with error msg
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur.args[0]}")

#  the finally clause is always executed
#  whether or not there is an exception
essai += 1
x = None
try:
    x = 1
except:
    #  exception
    print(f"essai n° {essai} : exception")
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    #  exception
    print(f"essai n° {essai} : exception")
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

#  we don't have to include an except clause
essai += 1
try:
    #  we cause an error
    x = 4 / 0
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

Note:

  • righe 4–12: gestione della divisione per zero;
  • riga 8: intercettiamo l'eccezione che si verifica e la visualizziamo;
  • riga 12: a causa dell'eccezione verificatasi, x non ha ricevuto un valore alla riga 7 e quindi non ha modificato il proprio valore;
  • righe 14–21: facciamo la stessa cosa ma intercettiamo un'eccezione di livello superiore di tipo BaseException. Poiché l'eccezione ZeroDivisionError è una sottoclasse di BaseException, la clausola except la intercetterà;
  • righe 23–36: utilizziamo più clausole except per gestire più tipi di eccezioni. Verrà eseguita solo una clausola except, oppure nessuna se l'eccezione non corrisponde a nessuna clausola except;
  • righe 38–50: Facciamo la stessa cosa, cambiando l'ordine delle clausole [except] per illustrarne il ruolo;
  • righe 52–58: La clausola `except` può non avere argomenti. In questo caso, intercetta tutte le eccezioni;
  • righe 60–67: introduciamo l'eccezione ValueError;
  • righe 69–75: recuperiamo le informazioni contenute nell'eccezione;
  • righe 77–83: introduciamo come generare un'eccezione;
  • righe 86–106: illustriamo l'uso di una classe di eccezione personalizzata, MyError. La classe MyError eredita semplicemente dalla classe base BaseException. Non aggiunge nulla alla sua classe base. Tuttavia, ora può essere esplicitamente nominata nelle clausole except;
  • righe 108–130: illustrano l'uso della clausola finally;
  • righe 132–139: queste righe mostrano che la clausola [except] non è obbligatoria;

L'output sullo schermo è il seguente:

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 come le eccezioni vengono propagate lungo la catena delle funzioni chiamanti:

#  a proprietary exception
class MyError(BaseException):
    pass


#  three functions
def f1(x: int) -> int:
    #  exceptions are not handled - they are automatically raised
    return f2(x)


def f2(y: int) -> int:
    #  exceptions are not handled - they are automatically raised
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        #  if z is even, throw an exception
        raise MyError("exception dans f3")
    else:
        return 2 * z


#  ---------- hand

#  exceptions go up the chain of methods called
#  until a method intercepts it. In this case, it will be main
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


#  three other functions that enrich the exceptions it tracks
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        #  we enrich the exception then relaunch it
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        #  we enrich the exception then relaunch it
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        #  throw an exception if z is even
        raise MyError("exception dans f6")
    else:
        return 2 * z


#  ---------- hand
try:
    print(f4(4))
except MyError as erreur:
    #  exception display
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    #  you can move up the exception stack
    err = erreur
    #  error msg is displayed
    print(err.args[0])
    #  is an exception encapsulated?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        #  exception change
        err = err.args[1]
        #  the 1st argument is the error msg
        print(err.args[0])

Note:

  • righe 25–32: nella chiamata main → f1 → f2 → f3 (riga 30), l'eccezione MyError generata da f3 si propagherà fino a main. Verrà quindi gestita dalla clausola except alla riga 31;
  • righe 61–75: nella chiamata main → f4 → f5 → f6 (riga 62), l'eccezione MyError generata da f6 si propagherà fino a main. Verrà quindi gestita dalla clausola except alla riga 63. Questa volta, mentre si propaga lungo la catena delle funzioni chiamanti, l'eccezione MyError che si propaga è a sua volta incapsulata all'interno di un'altra eccezione;
  • righe 66–75: mostrano come tracciare lo stack delle eccezioni;
  • riga 71: la funzione [isinstance(instance, Class)] restituisce True se l'oggetto [instance] è di tipo [Class] o di una sottoclasse. Qui abbiamo utilizzato l'eccezione di livello più alto [BaseException], che garantisce la cattura di tutte le eccezioni;

L'output sullo schermo è il seguente:

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