Ausnahmebehandlung in Python – Teil 1

Wie auch in anderen Programmiersprachen, existiert in Python das Programmierkonzept der Ausnahmebehandlung (exception handling). Worum geht es dabei? Zur Laufzeit eines Programms können Fehler auftreten. Betrachten wir dazu folgendes Beispiel:

>>> x = 100
>>> y = "Tee"
>>> print(x + y)
Traceback (most recent call last):
    File "<pyshell#4>", line 1, in <module>
        print(x + y)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Die Addition von x und y resultiert in einem TypeError. Denn es wird versucht eine Ganzzahl (int) mit einer Zeichenfolge (str) zu addieren. Der Fehler — die Ausnahme — besteht hier in den unterschiedlichen Datentypen, die eine Addition nicht zulassen.

Immer wenn das Risiko eines Fehlers besteht, bietet es sich an, eine Ausnahmebehandlung zu implementieren. Dies wird durch das Hinzufügen eines tryexcept-Blocks erreicht. Bezogen auf das obige Beispiel, könnte dies so aussehen:

x = 100
y = "Tee"

try:
    print(x + y)
except:
    print("Es ist ein Fehler aufgetreten!")

Anstelle der bisherigen Fehlermeldung bzw. dem Traceback

Traceback (most recent call last):
    File "<pyshell#4>", line 1, in <module>
        print(x + y)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

erscheint jetzt folgende Meldung:

Es ist ein Fehler aufgetreten!

Denn das Ausführen des Codes im try-Block scheitert. Mithilfe des except-Zweigs wird jetzt der Fehler abgefangen, d.h. es wird jetzt die print()-Funktion aufgerufen.

Falls bei Euch ein Linter, z.B. flake8 eingerichtet sein sollte, dann wird in der Zeile except: außerdem folgender Hinweis erscheinen:

do not use bare 'except'

Damit ist gemeint, dass es besser ist, den Art des Fehlers zu benennen:

except TypeError:

Außerdem ist es üblich hinter dem Ausnahmetyp eine Variable anzugeben (häufig e oder ex):

except TypeError as e:

Dies ermöglicht es, auf die im Rahmen der Ausnahmebehandlung generierte Fehlermeldung zuzugreifen. Der komplette tryexcept-Block könnte dann wie folgt aussehen:

try:
    print(x + y)
except TypeError as e:
    print("Es ist ein Fehler aufgetreten!")
    print(e)

Die Variable e wird als Argument an eine print-Funktion übergeben. Das Ausführen dieses Codes führt jetzt zu folgender Ausgabe:

Es ist ein Fehler aufgetreten!
unsupported operand type(s) for +: 'int' and 'str'

Die Geschichte ist damit aber noch nicht zu Ende, denn es existiert noch das Schlüsselwort raise. Damit wird die Ausnahme an den Aufrufer hochgereicht, wie folgendes Beispiel verdeutlicht:

def add(x, y):
    try:
        print(x + y)
    except TypeError as e:
        print("Es ist ein Fehler aufgetreten!")
        print(e)
        raise

try:
    add(100, "Tee")  # <- Aufruf der Funktion add()
except TypeError as e:
    print(f"Fehlermeldung:\n{e}")

Die Fehlermeldung erscheint jetzt zweimal:

Es ist ein Fehler aufgetreten!
unsupported operand type(s) for +: 'int' and 'str'
Fehlermeldung:
unsupported operand type(s) for +: 'int' and 'str'

Denn aufgrund von raise endet das Programm nicht mit der Ausführung des except-Zweigs der Funktion add(). Vielmehr wird jetzt auch der except-Zweig des Aufrufers ausgeführt, also zusätzlich die Zeile

print(f"Fehlermeldung:\n{e}")

ausgewertet.

In dem hier gezeigten Beispiel mag das ganze wenig Sinn ergeben. Anders sieht es aber aus, wenn ein Nutzer zu einer Eingabe aufgefordert wird. Können wir diesbezüglich sichergehen, dass die Nutzerin oder Nutzer tatsächlich eine Zahl eingibt? Nein, es besteht immer das Risiko, dass irgendetwas anderes eingegeben wird, was das Programm zum Absturz bringen könnte. Dieses Risiko kann durch eine Ausnahmebehandlung minimiert werden.

Als Einführung soll es das an dieser Stelle gewesen sein. Für weitere Details und Beispiele empfehle ich einen Blick in die Python-Dokumentation.