Skip to content

5. Die Ausnahmen

Wir wenden uns nun den Ausnahmen zu.

Image

5.1. Skript [exceptions_01]

Das erste Skript veranschaulicht die Notwendigkeit, Ausnahmen zu behandeln.

# Es wird absichtlich ein Fehler ausgelöst
x = 4 / 0

Wir lösen absichtlich einen Fehler aus, um zu sehen, was passiert (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

Zeile 4 gibt uns:

  • den Typ der Ausnahme: ZeroDivisionError;
  • die zugehörige Fehlermeldung: „division by zero“. Sie ist auf Englisch. Das möchte man vielleicht ändern.

Eine wichtige Regel lautet, dass man alles tun muss, um vom Python-Interpreter ausgelöste Ausnahmen zu vermeiden. Wir müssen die Fehler selbst behandeln.

Die Syntax für die Ausnahmebehandlung lautet wie folgt:


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

Im **try**-Block wird die Ausführung der Aktionen unterbrochen, sobald eine Ausnahme (ein Fehler) auftritt. In diesem Fall wird die Ausführung mit den Aktionen einer der except-Klauseln fortgesetzt:

  • Zeile 3: Wenn die aufgetretene Ausnahme [ex] zu einem Typ gehört, der im Tupel (Ex1, Ex2…) enthalten ist oder von einem dieser Typen abgeleitet ist, werden die Aktionen in Zeile 4 ausgeführt;
  • Zeile 5: Wenn die Ausnahme nicht von Zeile 3 abgefangen wurde und eine weitere Klausel [except] existiert, läuft derselbe Prozess ab. Usw.;
  • Es kann so viele [except]-Klauseln geben, wie nötig sind, um die verschiedenen Ausnahmetypen zu behandeln, die im [try] auftreten können;
  • Wurde die Ausnahme von keiner der Klauseln [except] behandelt, wird sie an den aufrufenden Code weitergeleitet. Befindet sich dieser selbst in einer try/except-Struktur, wird die Ausnahme erneut behandelt; andernfalls wird sie weiter die Kette der aufgerufenen Methoden hinaufgeleitet. Als letztes gelangt sie zum Python-Interpreter. Dieser bricht daraufhin das ausgeführte Programm ab und zeigt eine Fehlermeldung an, wie sie im vorherigen Beispiel gezeigt wurde. Die Regel lautet daher, dass das Hauptprogramm alle Ausnahmen abfangen muss, die von den aufgerufenen Methoden weitergeleitet werden können;
  • Zeile 7: Die Anweisung [finally] wird immer ausgeführt, unabhängig davon, ob eine Ausnahme aufgetreten ist (Fortsetzung des „except“-Blocks) oder nicht (Fortsetzung des „try“-Blocks). Dies gilt auch dann, wenn eine Ausnahme aufgetreten ist und nicht abgefangen wurde. In diesem Fall wird die Klausel [finally] ausgeführt, bevor die Ausnahme an den aufrufenden Code weitergeleitet wird;

Eine Ausnahme enthält Informationen über den aufgetretenen Fehler. Diese können mit der folgenden Syntax abgerufen werden:


except MyException as exception:

[exception] ist die aufgetretene Ausnahme. [exception.args] stellt das Tupel der Ausnahmeparameter dar.

Um eine Ausnahme auszulösen, verwendet man die Syntax


raise MyException(param1, param2…)

wobei MyException in den meisten Fällen eine von der Klasse BaseException abgeleitete Klasse ist. Die an den Konstruktor der Klasse übergebenen Parameter stehen in der „except“-Klausel von Ausnahmebehandlungsstrukturen mit der Syntax [ex.args] zur Verfügung, wenn [ex] die von der Klausel [except] abgefangene Ausnahme ist.

Diese Konzepte werden durch das folgende Skript veranschaulicht.

5.2. Skript [exceptions_02]

Das folgende Skript behandelt Fehler explizit:


# Ausnahmebehandlung
essai = 0

# Ein Fehler wird ausgelöst und behandelt
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    # Der Fehler ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}")
# Der Wert von x hat sich nicht geändert
print(f"x={x}")

# Man beginnt von vorne
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    # Die allgemeinste Ausnahme wird abgefangen
    # Der Fehler ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}")

# Es können verschiedene Arten von Ausnahmen abgefangen werden
# Die Ausführung wird bei der ersten Instanz von [except] angehalten, die die Ausnahme verarbeiten kann
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # Diese Ausnahme tritt hier nicht auf
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    # Die allgemeinste Ausnahme wird abgefangen
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    # Ein bestimmter Typ wird abgefangen
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

# Wir versuchen es erneut und ändern die Reihenfolge 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # Diese Ausnahme tritt hier nicht auf
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    # Man fängt einen bestimmten Typ ab
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    # Die allgemeinste Ausnahme wird abgefangen
    print(f"essai n° {essai} : (Exception) {erreur}")

# eine „except“-Klausel ohne Argumente
essai += 1
try:
    x = 4 / 0
except:
    # Die Art der Ausnahme spielt keine Rolle
    print(f"essai n° {essai} : il y a eu un problème")

# ein anderer Ausnahmetyp
essai += 1
try:
    # x kann nicht in eine Ganzzahl konvertiert werden
    x = int("x")
except ValueError as erreur:
    # „Fehler“ ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}")

# Eine Ausnahme enthält Informationen in einem Tupel, auf das das Programm zugreifen kann
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    # „Fehler“ ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# Ausnahmen können ausgelöst werden
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    # Der Fehler ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


# Man kann eigene Ausnahmen erstellen
# sie müssen von der Klasse [BaseException] abgeleitet sein
class MyError(BaseException):
    pass


# Die Ausnahme MyError wird ausgelöst
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    # Der Fehler ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# Die Ausnahme MyError wird mit einer Fehlermeldung ausgelöst
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    # Der Fehler ist die abgefangene Ausnahme
    print(f"essai n° {essai} : {erreur.args[0]}")

# Die finally-Klausel wird immer ausgeführt
# unabhängig davon, ob eine Ausnahme vorliegt oder nicht
essai += 1
x = None
try:
    x = 1
except:
    # Ausnahme
    print(f"essai n° {essai} : exception")
finally:
    # wird in jedem Fall ausgeführt
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    # Ausnahme
    print(f"essai n° {essai} : exception")
finally:
    # wird in jedem Fall ausgeführt
    print(f"essai n° {essai} : finally x={x}")

# Es ist nicht zwingend erforderlich, eine Klausel einzufügen [except]
essai += 1
try:
    # löst einen Fehler aus
    x = 4 / 0
finally:
    # wird in jedem Fall ausgeführt
    print(f"essai n° {essai} : finally x={x}")

Hinweise:

  • Zeilen 4–12: Eine Division durch Null wird behandelt;
  • Zeile 8: Die aufgetretene Ausnahme wird abgefangen und angezeigt;
  • Zeile 12: Aufgrund der aufgetretenen Ausnahme hat x in Zeile 7 keinen Wert erhalten und hat daher seinen Wert nicht geändert;
  • Zeilen 14–21: Wir wiederholen den Vorgang, fangen dabei jedoch eine Ausnahme höherer Ebene vom Typ BaseException ab. Da die Ausnahme ZeroDivisionError von der Klasse BaseException abgeleitet ist, wird sie durch die Klausel except abgefangen;
  • Zeilen 23–36: Es werden mehrere Klauseln vom Typ except eingefügt, um verschiedene Ausnahmetypen zu behandeln. Es wird entweder eine einzige Klausel vom Typ except ausgeführt oder gar keine, wenn die Ausnahme keine der „except“-Klauseln erfüllt;
  • Zeilen 38–50: Wir wiederholen denselben Vorgang, ändern dabei jedoch die Reihenfolge der Klauseln [except], um deren Funktion zu veranschaulichen;
  • Zeilen 52–58: Die Klausel except kann ohne Argumente auskommen. In diesem Fall stoppt sie alle Ausnahmen;
  • Zeilen 60–67: Einführung der Ausnahme ValueError;
  • Zeilen 69–75: Die von der Ausnahme übertragenen Informationen werden abgerufen;
  • Zeilen 77–83: Hier wird beschrieben, wie eine Ausnahme ausgelöst (raise) wird;
  • Zeilen 86–106: veranschaulichen die Verwendung einer proprietären Ausnahmeklasse MyError. Die Klasse MyError leitet lediglich von der Basisklasse BaseException ab. Sie fügt ihrer Basisklasse nichts hinzu. Nun kann sie jedoch explizit in den „except“-Klauseln genannt werden;
  • Zeilen 108–130: veranschaulichen die Verwendung der Klausel finally;
  • Zeilen 132–139: Diese Zeilen zeigen, dass die Klausel [except] nicht obligatorisch ist;

Die Bildschirmausgaben lauten wie folgt:

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. Skript [exceptions_03]

Dieses neue Skript veranschaulicht die Weiterleitung von Ausnahmen in der Kette der aufrufenden Funktionen:


# eine benutzerdefinierte Ausnahme
class MyError(BaseException):
    pass


# drei Funktionen
def f1(x: int) -> int:
    # Ausnahmen werden nicht behandelt – sie werden automatisch weitergeleitet
    return f2(x)


def f2(y: int) -> int:
    # Ausnahmen werden nicht behandelt – sie werden automatisch weitergeleitet
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        # Wenn z gerade ist, wird eine Ausnahme ausgelöst
        raise MyError("exception dans f3")
    else:
        return 2 * z


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

# Die Ausnahmen werden die Kette der aufgerufenen Methoden hinauf weitergeleitet
# bis eine Methode sie abfängt. Hier ist das „main“
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


# Drei weitere Funktionen, die die weitergeleiteten Ausnahmen ergänzen
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        # Die Ausnahme wird ergänzt und anschließend weitergeleitet
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        # Die Ausnahme wird erweitert und anschließend erneut ausgelöst
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        # Es wird eine Ausnahme ausgelöst, wenn z gerade ist
        raise MyError("exception dans f6")
    else:
        return 2 * z


# ---------- main
try:
    print(f4(4))
except MyError as erreur:
    # Anzeige der Ausnahme
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    # Man kann den Ausnahmestapel zurückverfolgen
    err = erreur
    # Die Fehlermeldung wird angezeigt
    print(err.args[0])
    # Ist eine Ausnahme gekapselt?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        # Änderung der Ausnahme
        err = err.args[1]
        # Das erste Argument ist die Fehlermeldung
        print(err.args[0])

Anmerkungen:

  • Zeilen 25–32: Beim Aufruf von main --> f1 --> f2 --> f3 (Zeile 30) wird die von f3 ausgelöste Ausnahme MyError bis zu main weitergeleitet. Sie wird dann durch die Klausel except in Zeile 31 verarbeitet;
  • Zeilen 61–75: Beim Aufruf von main --> f4 --> f5 --> f6 (Zeile 62) wird die von f6 ausgelöste Ausnahme MyError bis zu main weitergeleitet. Sie wird dann von der Klausel except in Zeile 63 verarbeitet. Diesmal ist die Ausnahme MyError, die bei der Rückverfolgung der Kette der aufrufenden Funktionen nach oben gelangt, selbst in eine andere Ausnahme gekapselt;
  • Zeilen 66–75: zeigen, wie der Ausnahmestapel abgearbeitet wird;
  • Zeile 71: Die Funktion [isinstance(instance, Classe)] gibt „True“ zurück, wenn das Objekt [instance] vom Typ [Classe] oder davon abgeleitet ist. Hier haben wir die oberste Ausnahme „[BaseException]“ verwendet, wodurch sichergestellt ist, dass alle Ausnahmen erfasst werden;

Die Bildschirmausgaben lauten wie folgt:

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