5. De uitzonderingen
We gaan nu in op de uitzonderingen.

5.1. script [exceptions_01]
Het eerste script illustreert de noodzaak om uitzonderingen af te handelen.
We veroorzaken opzettelijk een fout om te zien wat er gebeurt (exceptions-01.py):
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 detry-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:
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: