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.
