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
tryundexcept) 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
tryKlausel eine Ausnahme auftritt, wird der Rest der Klausel übersprungen. Wenn der Typ der Ausnahme mit der nach demexceptSchlü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
trystatements; 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
tryKlausel eine Ausnahme auftritt, kann die Ausnahme von einerexceptKlausel behandelt werden. Wenn die Ausnahme nicht durch eineexceptKlausel behandelt wird, wird die Ausnahme erneut ausgelöst, nachdem diefinallyKlausel ausgeführt wurde.Während der Ausführung einer
exceptoderelseKlausel kann eine Ausnahme auftreten. Auch hier wird die Ausnahme erneut ausgelöst, nachdem diefinallyKlausel ausgeführt wurde.If the
finallyclause executes abreak,continueorreturnstatement, exceptions are not re-raised.Wenn die
tryAnweisung einebreak,continueoderreturnAnweisung erreicht, wird diefinallyKlausel direkt vor der Ausführung derbreak,continueoderreturnAnweisung ausgeführt.If a
finallyclause includes areturnstatement, the returned value will be the one from thefinallyclause’sreturnstatement, not the value from thetryclause’sreturnstatement.
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.