Skip to content

5. De uitzonderingen

We gaan nu in op de uitzonderingen.

Image

5.1. script [exceptions_01]

Het eerste script illustreert de noodzaak om uitzonderingen af te handelen.

# er wordt opzettelijk een fout veroorzaakt
x = 4 / 0

We veroorzaken opzettelijk een fout om te zien wat er gebeurt (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

Regel 4 geeft ons:

  • het type van de uitzondering: ZeroDivisionError;
  • de bijbehorende foutmelding: division by zero. Deze is in het Engels. Dit is iets wat we wellicht willen aanpassen.

Een essentiële regel is dat we er alles aan moeten doen om uitzonderingen die door de Python-interpreter worden gegenereerd te vermijden. We moeten de fouten zelf afhandelen.

De syntaxis voor uitzonderingsafhandeling is als volgt:


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

In de **try**-blok wordt de uitvoering van de acties gestopt zodra er een uitzondering (fout) optreedt. In dat geval gaat de uitvoering verder met de acties van een van de except-clausules:

  • regel 3: als de opgetreden uitzondering [ex] van een type is dat behoort tot de tuple (Ex1, Ex2…) of daarvan is afgeleid, dan worden de acties van regel 4 uitgevoerd;
  • regel 5: als de uitzondering niet door regel 3 is opgevangen en er een andere [except]-clausule bestaat, dan verloopt het proces op dezelfde manier. Enz…;
  • er kunnen zoveel [except]-clausules zijn als nodig is om de verschillende soorten uitzonderingen af te handelen die zich in de [try] kunnen voordoen;
  • als de uitzondering door geen van de [except]-clausules is afgehandeld, wordt deze doorgegeven aan de aanroepende code. Als deze zelf in een try/except-structuur staat, wordt de uitzondering opnieuw afgehandeld; anders wordt deze verder doorgegeven in de keten van aangeroepen methoden. Als laatste redmiddel komt de uitzondering bij de Python-interpreter terecht. Deze stopt dan het uitgevoerde programma en geeft een foutmelding weer zoals in het vorige voorbeeld. De regel is dus dat het hoofdprogramma alle uitzonderingen moet opvangen die vanuit de aangeroepen methoden kunnen worden doorgegeven;
  • regel 7: de clausule [finally] wordt altijd uitgevoerd, ongeacht of er een uitzondering is opgetreden (vervolg van de except-blok) of niet (vervolg van de try-blok). Dit geldt zelfs als er een uitzondering is opgetreden die niet is opgevangen. In dat geval wordt de clausule [finally] uitgevoerd voordat de uitzondering wordt doorgegeven aan de aanroepende code;

Een uitzondering bevat informatie over de fout die zich heeft voorgedaan. Deze informatie kan met de volgende syntaxis worden opgehaald:


except MyException as exception:

[exception] is de uitzondering die zich heeft voorgedaan. [exception.args] vertegenwoordigt het tupel van de parameters van de uitzondering.

Om een uitzondering te genereren, gebruikt men de syntaxis


raise MyException(param1, param2…)

waarbij MyException meestal een klasse is die is afgeleid van de klasse BaseException. De parameters die aan de constructor van de klasse worden doorgegeven, zijn beschikbaar in de except-clausule van de structuren voor het opvangen van uitzonderingen met de syntaxis [ex.args], als [ex] de uitzondering is die wordt opgevangen door de clausule [except].

Deze concepten worden geïllustreerd door het volgende script.

5.2. script [exceptions_02]

Het volgende script behandelt fouten expliciet:


# uitzonderingsafhandeling
essai = 0

# er wordt een fout veroorzaakt en deze wordt afgehandeld
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    # de fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}")
# de waarde van x is niet veranderd
print(f"x={x}")

# we beginnen opnieuw
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    # de meest algemene uitzondering wordt opgevangen
    # fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}")

# er kunnen verschillende soorten uitzonderingen worden opgevangen
# de uitvoering stopt bij de eerste [except] die de uitzondering kan afhandelen
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # deze uitzondering doet zich hier niet voor
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    # de meest algemene uitzondering wordt opgevangen
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    # er wordt een specifiek type opgevangen
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

# we beginnen opnieuw en veranderen de volgorde 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # deze uitzondering treedt hier niet op
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    # we vangen een specifiek type af
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    # de meest algemene uitzondering wordt opgevangen
    print(f"essai n° {essai} : (Exception) {erreur}")

# een except-clausule zonder argumenten
essai += 1
try:
    x = 4 / 0
except:
    # de aard van de uitzondering is niet van belang
    print(f"essai n° {essai} : il y a eu un problème")

# een ander type uitzondering
essai += 1
try:
    # x kan niet worden omgezet naar een geheel getal
    x = int("x")
except ValueError as erreur:
    # 'fout' is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}")

# een uitzondering bevat informatie in een tuple die toegankelijk is voor het programma
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    # fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# er kunnen uitzonderingen worden gegenereerd
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    # de fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


# men kan eigen uitzonderingen maken
# deze moeten afstammen van de klasse [BaseException]
class MyError(BaseException):
    pass


# de uitzondering MyError wordt gegooid
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    # de fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# de uitzondering MyError wordt gegenereerd met een foutmelding
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    # de fout is de opgevangen uitzondering
    print(f"essai n° {essai} : {erreur.args[0]}")

# de finally-clausule wordt altijd uitgevoerd
# ongeacht of er een uitzondering is of niet
essai += 1
x = None
try:
    x = 1
except:
    # uitzondering
    print(f"essai n° {essai} : exception")
finally:
    # wordt in alle gevallen uitgevoerd
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    # uitzondering
    print(f"essai n° {essai} : exception")
finally:
    # wordt in alle gevallen uitgevoerd
    print(f"essai n° {essai} : finally x={x}")

# het is niet verplicht om een clausule [except] op te nemen
essai += 1
try:
    # dit veroorzaakt een fout
    x = 4 / 0
finally:
    # wordt in alle gevallen uitgevoerd
    print(f"essai n° {essai} : finally x={x}")

Opmerkingen:

  • regels 4-12: er wordt omgegaan met een deling door nul;
  • regel 8: de exacte uitzondering die optreedt, wordt opgevangen en weergegeven;
  • regel 12: vanwege de opgetreden uitzondering heeft x geen waarde ontvangen in regel 7 en is de waarde dus niet gewijzigd;
  • regels 14-21: we doen hetzelfde nogmaals, maar vangen nu een uitzondering van een hoger niveau van het type BaseException op. Aangezien de uitzondering ZeroDivisionError is afgeleid van de klasse BaseException, zal de clausule except deze stoppen;
  • regels 23-36: er worden meerdere except-clausules geplaatst om verschillende soorten uitzonderingen af te handelen. Er wordt slechts één except-clausule uitgevoerd, of geen enkele als de uitzondering aan geen enkele except-clausule voldoet;
  • regels 38-50: we herhalen hetzelfde door de volgorde van de [except]-clausules te wijzigen om de rol ervan te illustreren;
  • regels 52-58: de clausule except hoeft geen argumenten te hebben. In dat geval stopt deze alle uitzonderingen;
  • regels 60-67: introduceren de uitzondering ValueError;
  • regels 69-75: de informatie die door de uitzondering wordt meegevoerd, wordt opgehaald;
  • regels 77-83: hier wordt uitgelegd hoe een uitzondering kan worden gegenereerd (raise);
  • regels 86-106: illustreren het gebruik van een eigen uitzonderingsklasse MyError. De klasse MyError is slechts afgeleid van de basisklasse BaseException. Ze voegt niets toe aan haar basisklasse. Maar nu kan ze expliciet worden genoemd in de except-clausules;
  • regels 108-130: illustreren het gebruik van de clausule finally;
  • regels 132-139: deze regels laten zien dat de clausule [except] niet verplicht is;

De schermresultaten zijn als volgt:

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]

Dit nieuwe script illustreert het doorgeven van uitzonderingen in de keten van aanroepende functies:


# een eigen uitzondering
class MyError(BaseException):
    pass


# drie functies
def f1(x: int) -> int:
    # uitzonderingen worden niet afgehandeld – ze worden automatisch doorgegeven
    return f2(x)


def f2(y: int) -> int:
    # uitzonderingen worden niet afgehandeld – ze worden automatisch doorgegeven
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        # als z even is, wordt er een uitzondering gegenereerd
        raise MyError("exception dans f3")
    else:
        return 2 * z


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

# de uitzonderingen worden doorgegeven via de keten van aangeroepen methoden
# totdat een methode deze onderschept. Hier is dat main
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


# drie andere functies die de doorgegeven uitzonderingen aanvullen
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        # de uitzondering wordt aangevuld en vervolgens opnieuw doorgestuurd
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        # de uitzondering wordt aangevuld en vervolgens opnieuw gegenereerd
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        # er wordt een uitzondering gegenereerd als z even is
        raise MyError("exception dans f6")
    else:
        return 2 * z


# ---------- main
try:
    print(f4(4))
except MyError as erreur:
    # de uitzondering weergeven
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    # de uitzonderingsstapel kan worden doorlopen
    err = erreur
    # de foutmelding wordt weergegeven
    print(err.args[0])
    # is een uitzondering ingekapseld?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        # wijziging van uitzondering
        err = err.args[1]
        # het eerste argument is de foutmelding
        print(err.args[0])

Opmerkingen:

  • regels 25-32, in de aanroep main --> f1 --> f2 --> f3 (regel 30), wordt de uitzondering MyError, die door f3 is gegenereerd, doorgegeven naar main. Deze wordt vervolgens afgehandeld door de clausule except op regel 31;
  • regels 61-75: bij de aanroep main --> f4 --> f5 --> f6 (regel 62) zal de door f6 gegenereerde uitzondering MyError terugkeren naar main. Deze wordt vervolgens afgehandeld door de clausule except op regel 63. Deze keer wordt de uitzondering MyError, die tijdens het doorlopen van de keten van aanroepende functies naar boven komt, zelf ingekapseld in een andere uitzondering;
  • regels 66-75: laten zien hoe de uitzonderingsstapel wordt doorlopen;
  • regel 71: de functie [isinstance(instance, Classe)] retourneert True als het object [instance] van het type [Classe] of daarvan is afgeleid. Hier hebben we de uitzondering op het hoogste niveau, [BaseException], gebruikt, waardoor we er zeker van zijn dat we alle uitzonderingen opvangen;

De schermresultaten zijn als volgt:

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