8. Fehler und Ausnahmen

Bis jetzt wurden Fehlermeldungen nur erwähnt, aber wenn du die Beispiele ausprobiert hast, hast du wahrscheinlich einige gesehen. Es gibt (mindestens) zwei Arten von Fehlern, die man unterscheiden kann: Syntaxfehler und Ausnahmen.

8.1. Syntax Fehler

Syntaxfehler, auch bekannt als Parsing-Fehler, sind vielleicht die häufigste Art von Beschwerden, die man bekommt, wenn man Python noch lernt:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

The parser repeats the offending line and displays a little ‚arrow‘ pointing at the earliest point in the line where the error was detected. The error is caused by (or at least detected at) the token preceding the arrow: in the example, the error is detected at the function print(), since a colon (':') is missing before it. File name and line number are printed so you know where to look in case the input came from a script.

8.2. Ausnahmen

Auch wenn eine Anweisung oder ein Ausdruck syntaktisch korrekt ist, kann es zu einem Fehler kommen, wenn man versucht, sie auszuführen. Fehler, die während der Ausführung entdeckt werden, werden Ausnahmen genannt und sind nicht unbedingt fatal: Du wirst bald lernen, wie man sie in Python-Programmen behandelt. Die meisten Ausnahmen werden jedoch nicht von Programmen behandelt und führen zu Fehlermeldungen wie hier gezeigt:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

In der letzten Zeile der Fehlermeldung steht, was passiert ist. Ausnahmen gibt es in verschiedenen Typen, und der Typ wird als Teil der Meldung ausgegeben: Die Typen im Beispiel sind ZeroDivisionError, NameError und TypeError. Die als Ausnahmetyp ausgegebene Zeichenkette ist der Name der aufgetretenen integrierten Ausnahme. Dies gilt für alle integrierten Ausnahmen, muss aber nicht für benutzerdefinierte Ausnahmen gelten (obwohl es eine nützliche Konvention ist). Standardausnahmenamen sind integrierte Bezeichner (keine reservierten Schlüsselwörter).

Der Rest der Zeile enthält detaillierte Informationen über die Art der Ausnahme und deren Ursache.

Der vorangehende Teil der Fehlermeldung zeigt den Kontext, in dem die Ausnahme aufgetreten ist, in Form eines Stack-Tracebacks. Im Allgemeinen enthält er einen Stack-Traceback, der die Zeilen des Quellcodes auflistet; er zeigt jedoch keine Zeilen an, die von der Standardeingabe gelesen wurden.

Built-in Exceptions listet die integrierten Ausnahmen und ihre Bedeutungen auf.

8.3. Behandlung von Ausnahmen

Es ist möglich, Programme zu schreiben, die ausgewählte Ausnahmen behandeln. Schau dir das folgende Beispiel an, das den Benutzer zur Eingabe auffordert, bis ein gültiger Integer eingegeben wurde, dem Benutzer aber erlaubt, das Programm zu unterbrechen (mit Control-C oder was auch immer das Betriebssystem unterstützt); beachte, dass eine vom Benutzer erzeugte Unterbrechung durch das Auslösen der Ausnahme KeyboardInterrupt signalisiert wird.

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...

Die try Anweisung funktioniert wie folgt.

  • Zuerst wird die Try-Klausel (Die Anweisung(en) zwischen den Schlüsselwörtern try und except) ausgeführt.

  • Wenn keine Ausnahme auftritt, wird die except-Klausel übersprungen, und die Ausführung der try-Anweisung ist abgeschlossen.

  • Wenn während der Ausführung der try Klausel eine Ausnahme auftritt, wird der Rest der Klausel übersprungen. Wenn der Typ der Ausnahme mit der nach dem except Schlüsselwort genannten Ausnahme übereinstimmt, wird die Except-Klausel ausgeführt, und die Ausführung wird nach dem try/except-Block fortgesetzt.

  • If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.

A try statement may have more than one except clause, to specify handlers for different exceptions. At most one handler will be executed. Handlers only handle exceptions that occur in the corresponding try clause, not in other handlers of the same try statement. An except clause may name multiple exceptions as a parenthesized tuple, for example:

... except (RuntimeError, TypeError, NameError):
...     pass

A class in an except clause is compatible with an exception if it is the same class or a base class thereof (but not the other way around — an except clause listing a derived class is not compatible with a base class). For example, the following code will print B, C, D in that order:

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

Beachte, dass, wenn die Exception-Klauseln umgekehrt wären (mit except B zuerst), B, B, B ausgegeben worden wäre — die erste passende Exception-Klausel wird ausgelöst.

All exceptions inherit from BaseException, and so it can be used to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! It can also be used to print an error message and then re-raise the exception (allowing a caller to handle the exception as well):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except BaseException as err:
    print(f"Unexpected {err=}, {type(err)=}")
    raise

Alternatively the last except clause may omit the exception name(s), however the exception value must then be retrieved from sys.exc_info()[1].

Die try … except Anweisung hat eine optionale Else-Klausel, die, wenn vorhanden, allen Except-Klauseln folgen muss. Sie ist nützlich für Code, der ausgeführt werden muss, wenn die Try-Klausel keine Ausnahme auslöst. Zum Beispiel:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

Die Verwendung der else Klausel ist besser als das Hinzufügen von zusätzlichem Code zu der try Klausel, weil dadurch vermieden wird, dass versehentlich eine Ausnahme abgefangen wird, die nicht durch den Code ausgelöst wurde, der durch die try … except Anweisung geschützt wird.

When an exception occurs, it may have an associated value, also known as the exception’s argument. The presence and type of the argument depend on the exception type.

The except clause may specify a variable after the exception name. The variable is bound to an exception instance with the arguments stored in instance.args. For convenience, the exception instance defines __str__() so the arguments can be printed directly without having to reference .args. One may also instantiate an exception first before raising it and add any attributes to it as desired.

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    # the exception instance
...     print(inst.args)     # arguments stored in .args
...     print(inst)          # __str__ allows args to be printed directly,
...                          # but may be overridden in exception subclasses
...     x, y = inst.args     # unpack args
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

If an exception has arguments, they are printed as the last part (‚detail‘) of the message for unhandled exceptions.

Exception handlers don’t just handle exceptions if they occur immediately in the try clause, but also if they occur inside functions that are called (even indirectly) in the try clause. For example:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as err:
...     print('Handling run-time error:', err)
...
Handling run-time error: division by zero

8.4. Auslösen von Ausnahmen

Die raise Anweisung ermöglicht es dem Programmierer, das Auftreten einer bestimmten Ausnahme zu erzwingen. Zum Beispiel:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

The sole argument to raise indicates the exception to be raised. This must be either an exception instance or an exception class (a class that derives from Exception). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments:

raise ValueError  # shorthand for 'raise ValueError()'

Wenn du feststellen musst, ob eine Ausnahme ausgelöst wurde, aber nicht beabsichtigst, diese zu behandeln, kannst du mit einer einfacheren Form der raise Anweisung die Ausnahme erneut auslösen:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

8.5. Verkettung von Ausnahmen

Tritt eine unbehandelte Ausnahme innerhalb eines except Abschnitts auf, wird die behandelte Ausnahme an diesen Abschnitt angehängt und in die Fehlermeldung aufgenommen:

>>> try:
...     open("database.sqlite")
... except OSError:
...     raise RuntimeError("unable to handle error")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: unable to handle error

Um anzuzeigen, dass eine Ausnahme eine direkte Folge einer anderen ist, erlaubt die raise Anweisung eine optionale from Klausel:

# exc must be exception instance or None.
raise RuntimeError from exc

Dies kann nützlich sein, wenn du Ausnahmen umwandeln wikkst. Zum Beispiel:

>>> def func():
...     raise ConnectionError
...
>>> try:
...     func()
... except ConnectionError as exc:
...     raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in func
ConnectionError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database

Es erlaubt auch die Deaktivierung der automatischen Ausnahmeverkettung mit dem from None Idiom:

>>> try:
...     open('database.sqlite')
... except OSError:
...     raise RuntimeError from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError

Weitere Informationen zur Verkettungsmechanik findest du unter Built-in Exceptions.

8.6. Benutzerdefinierte Ausnahmen

Programme können ihre eigenen Ausnahmen benennen, indem sie eine neue Ausnahmeklasse erstellen (siehe Klassen für mehr über Python-Klassen). Ausnahmen sollten in der Regel von der Exception Klasse abgeleitet werden, entweder direkt oder indirekt.

Es können Ausnahmeklassen definiert werden, die alles tun können, was jede andere Klasse auch tun kann, aber sie sind in der Regel einfach gehalten und bieten oft nur eine Reihe von Attributen, die es ermöglichen, Informationen über den Fehler durch Handler für die Ausnahme zu extrahieren.

Die meisten Ausnahmen werden mit Namen definiert, die auf „Error“ enden, ähnlich der Benennung der Standardausnahmen.

Many standard modules define their own exceptions to report errors that may occur in functions they define. More information on classes is presented in chapter Klassen.

8.7. Definition von Bereinigungsaktionen

Die try Anweisung enthält eine weitere optionale Klausel, die dazu dient, Bereinigungsaktionen zu definieren, die unter allen Umständen ausgeführt werden müssen. Zum Beispiel:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt

Wenn eine finally Klausel vorhanden ist, wird die finally Klausel als letzte Aufgabe ausgeführt, bevor die try Anweisung abgeschlossen wird. Die finally Klausel wird unabhängig davon ausgeführt, ob die try Anweisung eine Ausnahme erzeugt oder nicht. In den folgenden Punkten werden komplexere Fälle beim Auftreten einer Ausnahme behandelt:

  • Wenn während der Ausführung der try Klausel eine Ausnahme auftritt, kann die Ausnahme von einer except Klausel behandelt werden. Wenn die Ausnahme nicht durch eine except Klausel behandelt wird, wird die Ausnahme erneut ausgelöst, nachdem die finally Klausel ausgeführt wurde.

  • Während der Ausführung einer except oder else Klausel kann eine Ausnahme auftreten. Auch hier wird die Ausnahme erneut ausgelöst, nachdem die finally Klausel ausgeführt wurde.

  • If the finally clause executes a break, continue or return statement, exceptions are not re-raised.

  • Wenn die try Anweisung eine break, continue oder return Anweisung erreicht, wird die finally Klausel direkt vor der Ausführung der break, continue oder return Anweisung ausgeführt.

  • If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement.

Zum Beispiel:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

Ein komplizierteres Beispiel:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

Wie du sehen kannst, wird die finally Klausel in jedem Fall ausgeführt. Der TypeError, ausgelöst durch die Teilung zweier Zeichenfolgenn wird von der except Klausel nicht verarbeitet und daher nach Ausführung der finally Klausel erneut ausgelöst.

In realen Anwendungen ist die finally Klausel nützlich, um externe Ressourcen (z. B. Dateien oder Netzwerkverbindungen) freizugeben, unabhängig davon, ob die Nutzung der Ressource erfolgreich war.

8.8. Vordefinierte Bereinigungsaktionen

Einige Objekte definieren Standardbereinigungsaktionen, die durchgeführt werden, wenn das Objekt nicht mehr benötigt wird, unabhängig davon, ob der Vorgang mit dem Objekt erfolgreich war oder fehlgeschlagen ist. Schau dir das folgende Beispiel an, in dem versucht wird, eine Datei zu öffnen und ihren Inhalt auf dem Bildschirm auszugeben.

for line in open("myfile.txt"):
    print(line, end="")

Das Problem bei diesem Code besteht darin, dass die Datei für eine unbestimmte Zeit geöffnet bleibt, nachdem die Ausführung dieses Teils des Codes abgeschlossen ist. Dies ist bei einfachen Skripten kein Problem, kann jedoch bei größeren Anwendungen ein Problem darstellen. Die with Anweisung ermöglicht die Verwendung von Objekten wie Dateien auf eine Weise, die sicherstellt, dass sie stets zeitnah und korrekt bereinigt werden.

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

Nach Ausführung der Anweisung wird die Datei f immer geschlossen, auch wenn bei der Verarbeitung der Zeilen ein Problem aufgetreten ist. Objekte, die wie Dateien vordefinierte Bereinigungsaktionen bereitstellen, weisen in ihrer Dokumentation darauf hin.