Skip to content

5. Ausnahmen

Wir wenden uns nun den Ausnahmen zu.

Image

5.1. Skript [exceptions_01]

Das erste Skript veranschaulicht die Notwendigkeit, Ausnahmen zu behandeln.

# on provoque volontairement une erreur
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 ergibt:

  • den Ausnahmetyp: ZeroDivisionError;
  • die zugehörige Fehlermeldung: Division durch Null. Sie ist auf Englisch. Das möchten Sie vielleicht ändern.

Eine wichtige Regel lautet, dass wir alles tun müssen, um vom Python-Interpreter ausgelöste Ausnahmen zu vermeiden. Wir müssen 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 beendet, 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 zum Tupel (Ex1, Ex2…) gehört 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 [except]-Klausel vorhanden ist, dann erfolgt derselbe Vorgang. Usw.;
  • Es kann so viele [except]-Klauseln geben, wie nötig sind, um die verschiedenen Arten von Ausnahmen zu behandeln, die innerhalb des [try]-Blocks auftreten können;
  • Wenn die Ausnahme von keiner der [except]-Klauseln behandelt wurde, wird sie an den aufrufenden Code zurückgegeben. Befindet sich der aufrufende Code selbst innerhalb einer try/except-Struktur, wird die Ausnahme erneut behandelt; andernfalls wird sie weiter die Kette der aufgerufenen Methoden hinaufgegeben. Letztendlich erreicht sie den Python-Interpreter. Der Interpreter beendet dann das laufende Programm und zeigt eine Fehlermeldung des im vorherigen Beispiel gezeigten Typs an. Die Regel lautet daher, dass das Hauptprogramm alle Ausnahmen abfangen muss, die von aufgerufenen Methoden weitergeleitet werden können;
  • Zeile 7: Die [finally]-Klausel wird immer ausgeführt, unabhängig davon, ob eine Ausnahme aufgetreten ist (nach dem except) oder nicht (nach dem try). Dies gilt auch dann, wenn eine Ausnahme aufgetreten ist und nicht abgefangen wurde. In diesem Fall wird die [finally]-Klausel ausgeführt, bevor die Ausnahme an den aufrufenden Code zurückgegeben wird;

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


except MyException as exception:

[exception] ist die aufgetretene Ausnahme. [exception.args] steht für das Tupel der Parameter der Ausnahme.

Um eine Ausnahme auszulösen, verwenden Sie die Syntax


raise MyException(param1, param2…)

wobei MyException meist eine von der Klasse BaseException abgeleitete Klasse ist. Die an den Klassenkonstruktor übergebenen Parameter stehen der except-Klausel von Ausnahmebehandlungsstrukturen unter Verwendung der Syntax [ex.args] zur Verfügung, wenn [ex] die von der [except]-Klausel abgefangene Ausnahme ist.

Diese Konzepte werden durch das folgende Skript veranschaulicht.

5.2. Skript [exceptions_02]

Das folgende Skript behandelt Fehler explizit:

#  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}")

Anmerkungen:

  • Zeilen 4–12: Behandlung einer Division durch Null;
  • Zeile 8: Wir fangen die aufgetretene Ausnahme ab und geben sie aus;
  • Zeile 12: Aufgrund der aufgetretenen Ausnahme hat x in Zeile 7 keinen Wert erhalten und daher seinen Wert nicht geändert;
  • Zeilen 14–21: Wir machen dasselbe, fangen jedoch eine übergeordnete Ausnahme vom Typ BaseException ab. Da die Ausnahme ZeroDivisionError eine Unterklasse von BaseException ist, wird sie von der except-Klausel abgefangen;
  • Zeilen 23–36: Wir verwenden mehrere except-Klauseln, um mehrere Ausnahmetypen zu behandeln. Es wird nur eine except-Klausel ausgeführt oder gar keine, wenn die Ausnahme keiner except-Klausel entspricht;
  • Zeilen 38–50: Wir verfahren genauso, ändern jedoch die Reihenfolge der [except]-Klauseln, um ihre Funktion zu veranschaulichen;
  • Zeilen 52–58: Die `except`-Klausel darf keine Argumente haben. In diesem Fall fängt sie alle Ausnahmen ab;
  • Zeilen 60–67: Einführung der ValueError-Ausnahme;
  • Zeilen 69–75: Wir rufen die von der Ausnahme enthaltenen Informationen ab;
  • Zeilen 77–83: Wir zeigen, wie man eine Ausnahme auslöst;
  • Zeilen 86–106: Veranschaulichung der Verwendung einer benutzerdefinierten Ausnahmeklasse, MyError. Die Klasse MyError erbt einfach von der Basisklasse BaseException. Sie fügt ihrer Basisklasse nichts hinzu. Sie kann nun jedoch explizit in except-Klauseln benannt werden;
  • Zeilen 108–130: veranschaulichen die Verwendung der finally-Klausel;
  • Zeilen 132–139: Diese Zeilen zeigen, dass die [except]-Klausel nicht zwingend erforderlich ist;

Die Bildschirmausgabe lautet 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, wie Ausnahmen in der Kette der aufrufenden Funktionen nach oben weitergeleitet werden:

#  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])

Anmerkungen:

  • Zeilen 25–32: Im Aufruf main → f1 → f2 → f3 (Zeile 30) wird die von f3 ausgelöste MyError-Ausnahme bis zu main weitergeleitet. Sie wird dann von der except-Klausel in Zeile 31 abgefangen;
  • Zeilen 61–75: Im Aufruf main → f4 → f5 → f6 (Zeile 62) wird die von f6 ausgelöste MyError-Ausnahme bis zu main weitergeleitet. Sie wird dann von der except-Klausel in Zeile 63 abgefangen. Da sie diesmal die Kette der aufrufenden Funktionen hinaufgeleitet wird, ist die weitergeleitete MyError-Ausnahme selbst in eine andere Ausnahme gekapselt;
  • Zeilen 66–75: zeigen, wie man den Ausnahmestapel nachverfolgt;
  • Zeile 71: Die Funktion [isinstance(instance, Class)] gibt True zurück, wenn das Objekt [instance] vom Typ [Class] oder einer Unterklasse ist. Hier haben wir die oberste Ausnahme [BaseException] verwendet, wodurch sichergestellt wird, dass wir alle Ausnahmen abfangen;

Die Bildschirmausgabe lautet 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