Ausnahmen

Auch wenn eine Anweisung oder ein Ausdruck syntaktisch korrekt ist, kann ein Fehler auftauchen, wenn diese Anweisung oder der Ausdruck ausgeführt wird. Fehler, die während der Ausführung erkannt werden, werden Ausnahmen (Exception) genannt und sind nicht bedingungslos verhängnisvoll: sie werden in diesem Kapitel sehen, wie man mit Ausnahmen in Python-Programmen umgehen kann. Die meisten Ausnahmen werden jedoch nicht in den Programmen abgefangen und führen daher zu Fehlermeldungen wie man hier sieht:

>>> 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't convert 'int' object to str implicitly

Die letzte Zeile jeder Fehlermeldung zeigt an, was passiert ist. Ausnahmen werden in verschiedene Typen eingeteilt, und der Typ wird als Teil der Nachricht ausgegeben: Die Typen in den obigen Beispielen sind

  • ZeroDivisionError,
  • NameError und
  • TypeError.

Die als Ausnahmetyp ausgegebene Zeichenkette ist der Name der vordefinierten Ausnahme, die aufgetreten ist. Dies gilt für alle vordefinierten (builin) Ausnahmen, muss aber nicht für benutzerdefinierte Ausnahmen gelten (obwohl es eine sinnvolle Konvention ist). Standard-Ausnahme-Namen sind vordefinierte Identifikatoren (und damit keine reservierten Schlüsselwörter).

Der Rest dieser letzten Zeile liefert Details die auf dem Typ der Ausnahme basieren und beschreibt, was die Ausnahme verursacht hat.

Der erste Teil der Fehlermeldung zeigt den Kontext an, in dem die Ausnahme stattgefunden hat. Dieser Kontext wird in der Form eines Stack-Tracebacks angezeigt. Im Allgemeinen enthält dieser Kontext eine Traceback (Fehlerrückverfolgung) Ausgabe mit der Auflistung der Zeilen im Quellcode, bei denen die Ausnahme passiert ist; Allerdings werden keine Zeilen ausgegeben, wenn der Python-Quellcode im Interpreter-Modus aus der Standard-Eingabe eingelesen wird (wie in den obigen Beispielen). Sowohl beim Aufruf von Python-Dateien aber auch im Jupyter-Notebook werden die Zeilen des Quellcodes aufgelistet und insbesondere die Zeile, in denen die Ausnahme passiert ist mit einer entsprechenden Pfeil-Markierung (---->) versehen, wie hier in den folgenden Beispielen zu sehen ist:

In [2]:
a = 1
b = 0
a/b
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-2-b6249a4a2b4f> in <module>()
      1 a = 1
      2 b = 0
----> 3 a/b

ZeroDivisionError: division by zero
In [3]:
a = 11
b = "2"
if a > 10:
    c = a+b
else:
    c = a*b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-413fcfaacb7d> in <module>()
      2 b = "2"
      3 if a > 10:
----> 4     c = a+b
      5 else:
      6     c = a*b

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Das Kapitel Built-in Exceptions (Vordefinierte Ausnahmen) in der Standard-Bibliothek listet alle vordefinierten Ausnahmen und deren Bedeutungen auf.

Umgang mit Ausnahmen

Es ist möglich, Programme zu schreiben, die bestimmte Ausnahmen behandeln. Schauen sie sich das folgende Beispiel an, das den Benutzer in der Eingabe solange fragt, bis eine gültige Ganzzahl eingegeben wurde, aber dem Benutzer auch erlaubt, das Programm zu unterbrechen (mit Control-C oder was auch immer das Betriebssystem als Unterbrechung eines Programms unterstützt); Beachten sie, dass eine vom Benutzer generierte Unterbrechung durch das Werfen einer KeyboardInterrupt-Ausnahme signalisiert wird.

In [4]:
while True:
    x = int(input("Bitte geben sie eine ganze Zahl ein: "))
    break
Bitte geben sie eine ganze Zahl ein: 5.6
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-1b7bd100dce4> in <module>()
      1 while True:
----> 2     x = int(input("Bitte geben sie eine ganze Zahl ein: "))
      3     break

ValueError: invalid literal for int() with base 10: '5.6'
In [5]:
while True:
    try:
        x = int(input("Bitte geben sie eine ganze Zahl ein: "))
        break
    except ValueError:
        print("Oops! Das war keine ganze Zahl ! Probieren sie es nochmal ...")
Bitte geben sie eine ganze Zahl ein: 5.6
Oops! Das war keine ganze Zahl ! Probieren sie es nochmal ...
Bitte geben sie eine ganze Zahl ein: 5

Wie lange benötigen sie, um einen Interrupt zu erzeugen ?

In [6]:
import datetime
x = 1
start = datetime.datetime.now()
while True:
    try:
        x += 1
    except KeyboardInterrupt:
        zeit = datetime.datetime.now() - start
        print("Zeit bis zum Interrupt: {} Sekunden - Schleife ist bis x={} gekommen".format(zeit.total_seconds(),x))
        break
Zeit bis zum Interrupt: 1.460965 Sekunden - Schleife ist bis x=14751332 gekommen

try

Die try-Anweisung funktioniert wie folgt:

  1. Zuerst wird der try-Block (die Anweisung(en) zwischen den try und except Schlüsselwörtern) ausgeführt.
  2. Wenn keine Ausnahme auftritt, wird der except-Block übersprungen und die Ausführung der try-Anweisung ist beendet.
  3. Wenn während der Ausführung des try-Blocks eine Ausnahme auftritt, wird der restliche Teil des Blocks übersprungen. Wenn dann der Typ mit der Ausnahme übereinstimmt, die nach dem except-Schlüsselwort aufgeführt ist, wird der except-Block ausgeführt und die Ausführung wird nach der try-Anweisung fortgesetzt.
  4. Wenn eine Ausnahme auftritt, die nicht mit der Ausnahme in dem except-Block übereinstimmt, wird sie an weitere except-Blöcke bzw. an eine äußere try-Anweisung weitergegeben. Wenn keine Möglichkeit gefunden wird, die Ausnahme zu behandeln, ist es eine unbehandelte Ausnahme und die Ausführung des Programms stoppt mit einer entsprechenden Ausnahme wie oben gezeigt.

Eine try-Anweisung kann mehr als einen exceptBlock haben, um verschiedene Ausnahmen unterschiedlich behandeln zu können. Ein except-Block kann dabei als Handler bezeichnet werden. Höchstens ein Handler wird ausgeführt. Handler behandeln nur Ausnahmen, die in dem entsprechenden try-Block auftreten, nicht in anderen Handlern der gleichen try-Anweisung. Ein except-Block kann mehrere Ausnahmen als Tupel benennen, wie hier zum Beispiel:

... 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:

Eine Klasse in einem except-Block ist mit einer Ausnahme kompatibel, wenn sie dieselbe Klasse oder eine Basisklasse davon ist (aber nicht umgekehrt - ein except-Block, der eine abgeleitete Klasse enthält, ist nicht mit einer Basisklasse kompatibel). Zum Beispiel wird der folgende Code B, C, D in dieser Reihenfolge drucken:

In [7]:
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")
B
C
D
In [8]:
class B():
    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")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-3aacb4cd12fc> in <module>()
     11     try:
---> 12         raise cls()
     13     except D:

TypeError: exceptions must derive from BaseException

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-8-3aacb4cd12fc> in <module>()
     11     try:
     12         raise cls()
---> 13     except D:
     14         print("D")
     15     except C:

TypeError: catching classes that do not inherit from BaseException is not allowed

Beachten sie, dass bei einer umgekehrten Reihenfolge der except-Blöcke (wenn also der except-Block B zuerst aufgeführt wird), die Ausgabe B, B, B sein wird - da immer die erste Übereinstimmung einer Ausnahme in einem except-Block ausgelöst wird:

In [9]:
class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

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

Wenn im letzten except-Block die Ausnahmenamen weggelassen werden, wird dieser Block verwendet um alle bisher nicht abgefangenen Ausnahmen zu behandeln. Verwenden sie diese Möglichkeit mit äußerster Vorsicht, da hiermit ein echter Programmierfehler maskiert werden könnte! Diese Möglichkeit kann aber auch dazu verwendet werden, um eine Fehlermeldung auszugeben und dann diese Ausnahme erneut zu werfen (so dass die aufrufende Funktion die Ausnahme ebenfalls behandeln kann), wie im folgenden Beispiel zu sehen ist:

In [10]:
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:
    print("Unexpected error:", sys.exc_info()[0])
    raise
OS error: [Errno 2] No such file or directory: 'myfile.txt'

Auf die try ... except Blöcke kann auch ein optionaler else-Block folgen, der, wenn vorhanden, nach allen except-Blöcken stehen muss. Dieser Block wird ausgeführt, wenn der try-Block keine Ausnahme auslöst. Beispielsweise:

In [11]:
%%writefile testOSError.py
import sys
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()
Writing testOSError.py
In [12]:
!python testOSError.py myfile.txt
cannot open myfile.txt
In [13]:
%%writefile myfile.txt
Dies ist ein Testfile
mit Text in
mehreren Zeilen
Writing myfile.txt
In [14]:
!python testOSError.py myfile.txt
myfile.txt has 3 lines

Die Verwendung diese else-Blocks ist besser als das Hinzufügen von zusätzlichen Code innerhalb des try-Blocks, weil diese Block versehentlich abgefangene Ausnahmen vermeidet, die nicht durch den Code in dem try-Block geschützt werden sollten.

Wenn eine Ausnahme auftritt, kann dieser Ausnahme ein dazugehöriger Wert mitgegeben werden, der auch als Argument der Ausnahme bezeichnet wird. Das Vorhandensein und die Art des Arguments hängen dabei vom Ausnahmetyp ab.

Der except-Block kann eine Variable nach dem Ausnahmennamen angeben. Die Variable ist an eine Ausnahmeinstanz gebunden, wobei die Argumente in instance.args gespeichert sind. Aus Gründen der Bequemlichkeit definiert die Ausnahmeinstanz die Methode __str__() so, dass die Argumente direkt ausgegeben werden, ohne dass sie auf .args verweisen müssen. Man kann auch zuerst eine Ausnahme instanziieren, bevor sie geworfen wird und irgendwelche Attribute wie gewünscht an die Instanz anhängt.

In [15]:
try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    # die Ausnahme Instanz
    print(inst.args)     # die Argumente werden in .args abgelegt
    print(inst)          # __str__ gibt .args direkt aus,
                         # aber kann in einer Ausnahme Unterklasse überschrieben werden
    x, y = inst.args     # unpack .args
    print('x =', x)
    print('y =', y)
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Wenn eine Ausnahme Argumente hat, werden diese als letzter Teil ('detail') der Nachricht für unbehandelte Ausnahmen ausgegeben.

Exception-Handler behandeln nicht nur Ausnahmen, wenn sie direkt im Code im try-Block auftreten, sondern auch, wenn sie innerhalb von Funktionen auftreten, die in dem try-Block (auch indirekt) aufgerufen werden. Beispielsweise:

In [16]:
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

Werfen von Ausnahmen (Raising Exceptions)

Die raise-Anweisung erlaubt dem Programmierer, eine bestimmte Ausnahme zu erzwingen. Beispielsweise:

In [17]:
raise NameError('HiThere')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-72c183edb298> in <module>()
----> 1 raise NameError('HiThere')

NameError: HiThere

Das einzige Argument von raise ist die Ausnahme die geworfen werden soll. Dies muss entweder eine Instanz einer Ausnahmeklasse oder eine Ausnahmeklasse selbst sein (eine Klasse, die aus einer Ausnahme abgeleitet wird). Wenn eine Ausnahmeklasse übergeben wird, wird sie implizit instanziiert, indem sie ihren Konstruktor ohne Argumente aufruft:

In [18]:
raise ValueError
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-e4c8e09828d5> in <module>()
----> 1 raise ValueError

ValueError: 

steht kurz für

In [19]:
raise ValueError()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-4954757c312d> in <module>()
----> 1 raise ValueError()

ValueError: 

Wenn Sie feststellen wollen, ob eine Ausnahme erhoben wurde, aber nicht beabsichtigen, diese zu behandeln, können Sie mit der raise-Anweisung ohne einem nachfolgenden Argument die Ausnahme wiederholen:

In [20]:
try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise
An exception flew by!
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-20-bf6ef4926f8c> in <module>()
      1 try:
----> 2     raise NameError('HiThere')
      3 except NameError:
      4     print('An exception flew by!')
      5     raise

NameError: HiThere
In [21]:
%%Mooc MoocStringAssessment
Out[21]:

Erweiterte Ausnahmebehandlung

Es ist folgender Code gegeben:

try:
    a = 7/0
except KeyboardInterrupt:
    print("Unterbrechung")
except:
    print("anderer Fehler")

Mit welcher Erweiterung in dem 2. except-Block können sie erreichen, den 'anderen Fehler' zu konkretisieren ?
Geben sie die entsprechende Anweisung an !



In [22]:
%%Mooc MoocStringAssessment
Out[22]:

traceback.format_exc()

Mit der Methode traceback.format_exc() aus dem Standardmodul traceback kann ebenfalls der 'andere Fehler' konkretisiert werden. traceback.format_exc() liefert eine Zeichenkette analog der, die bei einem raise-Aufruf ausgegeben wird. Mit dem folgenden Code erreichen sie daher ähnliche Informationen wie mit raise:

import traceback
try:
    a = 7/0
except KeyboardInterrupt:
    print("Unterbrechung")
except:
    print("anderer Fehler:")
    print(traceback.format_exc())

Welcher Vorteil ergibt sich hieraus gegenüber der Methode mit 'raise' ?
Geben sie an, ob das Programm bei dieser Methode weiterläuft (ja) oder nicht (nein):



In [23]:
%%Mooc MoocMultipleChoiceAssessment
Out[23]:

sys.exc_info()

Es gibt eine weitere Methode sys.exc_info() aus dem Standardmodul sys mit der ebenfalls der 'andere Fehler' konkretisiert werden kann. sys.exc_info() liefert ein Tupel (type, value, traceback) zurück. Daher können sie mit dem folgenden Code wieder ähnliche Informationen wie mit raise erreichen:

import sys
try:
    a = 7/0
except KeyboardInterrupt:
    print("Unterbrechung")
except:
    type, value, tb = sys.exc_info()
    print("{}: {}".format(type.__name__,value))
    print("Zeile:{}".format(tb.tb_lineno))
    tbCodeInfo = tb.tb_frame.f_code
    print("File '{}' in {}".format(tbCodeInfo.co_filename,tbCodeInfo.co_name))

Überlegen sie warum sich diese Methode zu statistischen Zwecken besser eignet als die Methode mit 'traceback.format_exc' ?

weil im Traceback (tb) mehr Informationen enthalten sind
weil in einem Dictionary der Fehlertyp als Schlüsselwert verwendet werden kann
weil das Standardmodul sys leichter importiert wird

Benutzerdefinierte Ausnahmen

Programme können eine eigene Ausnahme verwenden, indem sie eine neue Ausnahmeklasse erstellen. Ausnahmen sollten typischerweise direkt oder indirekt von der Klasse Exception abgeleitet werden.

Exception-Klassen können wie ganz normale Klassen definiert werden, sind aber in der Regel einfach gehalten, oft nur als zusätzliche Reihe von Attributen, in denen Informationen über den Fehler von Handlern für die Ausnahme extrahiert und bereitgestellt werden. Bei der Erstellung eines Moduls, das mehrere unterschiedliche Fehler auslösen kann, ist es eine gängige Praxis, eine Basisklasse für Ausnahmen in diesem Modul zu definieren und bestimmte Ausnahmeklassen für verschiedene Fehlerbedingungen jeweils als Unterklasse dieser Basisklasse zu erstellen, wie im folgenden Beispiel zu sehen ist:

In [24]:
class Error(Exception):
    """Basis Klasse für Ausnahmen in diesem Modul."""
    pass

class InputError(Error):
    """Ausnahmen die bei Eingabe Fehlern (InputError) geworfen werden.

    Attribute:
        expression -- Eingabe Ausdruck, in dem der Fehler aufgetaucht ist
        message -- Beschreibung des Fehlers
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Ausnahmen die bei Operationen geworfen werden, in denen versucht wird von einem Status in einen anderen
    Status zu gelangen (Status Übergang - State Transition), der aber nicht erlaubt ist.

    Attribute:
        previous -- Status zu Beginn der Operation
        next -- versuchter neuer Status
        message -- Beschreibung, warum der spezifische Übergang nicht erlaubt ist
        """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Ausnahmen sollten in der Regel mit Namen definiert werden, die in "Error" enden, ähnlich wie dies bei den Namen der Standardausnahmen der Fall ist.

Viele Standardmodule definieren ihre eigenen Ausnahmen, um Fehler zu melden, die innerhalb ihrer definierten Funktionen auftreten können.

Definieren von Aufräum-Aktionen (Clean-Up Actions)

Die try-Anweisung kann einen weiteren optionalen finally-Block verwenden, in dem Aufräum-Aktionen definiert werden, die unter allen Umständen ausgeführt werden müssen. Beispielsweise:

In [25]:
try:
    raise KeyboardInterrupt
finally:
    print('Goodbye, world!')
Goodbye, world!
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-25-ca8991ac7661> in <module>()
      1 try:
----> 2     raise KeyboardInterrupt
      3 finally:
      4     print('Goodbye, world!')

KeyboardInterrupt: 

Ein finally-Block wird immer ausgeführt, bevor die try-Anweisung verlassen wird - egal, ob eine Ausnahme aufgetreten ist oder nicht. Wenn eine Ausnahme in dem try-Block aufgetreten ist und nicht durch einen except-Block behandelt wurde (oder die Ausnahme ist in einem except- oder else-Block aufgetreten), wird diese Ausnahme nach der Ausführung des finally-Blocks erneut geworfen. Der finally-Block wird immer auf dem Weg aus der try-Anweisung heraus ausgeführt, also auch, wenn irgendein Block der try-Anweisung über ein break, continue oder return verlassen wird. Hier ein komplizierteres Beispiel:

In [26]:
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)
divide(2, 0)
divide("2", "1")
result is 2.0
executing finally clause
division by zero!
executing finally clause
executing finally clause
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-36a2d4607428> in <module>()
     11 divide(2, 1)
     12 divide(2, 0)
---> 13 divide("2", "1")

<ipython-input-26-36a2d4607428> in divide(x, y)
      1 def divide(x, y):
      2     try:
----> 3         result = x / y
      4     except ZeroDivisionError:
      5         print("division by zero!")

TypeError: unsupported operand type(s) for /: 'str' and 'str'

Wie sie sehen, wird der finally-Block auf jeden Fall ausgeführt. Der TypeError, der durch das Teilen von zwei Zeichenketten geworfen wird, wird durch den except-Block nicht behandelt und wird daher nach der Ausführung des finally-Blocks erneut geworfen.

In realen Anwendungen ist der finally-Block sinnvoll für die Freigabe von externen Ressourcen (wie Dateien oder Netzwerkverbindungen) verwendbar, unabhängig davon, ob die Ressource erfolgreich benutzt wurde oder nicht.

Vordefinierte Aufräum-Aktionen

Einige Objekte definieren Standard-Aufräum-Aktionen, die durchgeführt werden sollen, wenn das Objekt nicht mehr benötigt wird, unabhängig davon, ob die Operation mit dem Objekt erfolgreich war oder nicht. Schauen Sie sich das folgende Beispiel an, das versucht, eine Datei zu öffnen und ihren Inhalt auf den Bildschirm ausgibt:

In [27]:
for line in open("myfile.txt"):
    print(line, end="")
Dies ist ein Testfile
mit Text in
mehreren Zeilen
In [28]:
fd = open("myfile.txt")
for line in open("myfile.txt"):
    print(line, end="")
fd.closed
Dies ist ein Testfile
mit Text in
mehreren Zeilen
Out[28]:
False

Das Problem mit dem obigen Code ist, dass es die Datei für eine unbestimmte Zeit offen hält - auch noch nach dem dieser Code beendet worden ist. Dies ist kein Problem in einfachen Programmen, kann aber ein Problem für größere Anwendungen sein. Mit der with-Anweisung können Objekte wie Dateien in einer Weise verwendet werden, die sicherstellt, dass sie immer schnell und korrekt aufgeräumt werden:

In [29]:
with open("myfile.txt") as fdw:
    for line in fdw:
        print(line, end="")
fdw.closed
Dies ist ein Testfile
mit Text in
mehreren Zeilen
Out[29]:
True

Nachdem der with-Block ausgeführt worden ist, ist die Datei f immer geschlossen, auch wenn bei der Bearbeitung der Zeilen ein Problem aufgetreten ist. Objekte, die, wie Dateien, vordefinierte Aufräum-Aktionen bereitstellen, beschreiben diese auch in ihrer Dokumentation.

In [30]:
%%Mooc Video
Out[30]:

Weitere Literatur

In [31]:
%%Mooc WebReference

Errors and Exceptions

https://docs.python.org/3/tutorial/errors.html

Hinweis: Fehler und Ausnahmen