Fehler und Ausnahmen
Ausnahmebehandlung
Eine Ausnahme ist ein Fehler, der während der Ausführung eines Programms auftritt. Ausnahmen sind Nicht-Programmierern als Instanzen bekannt, die nicht mit einer allgemeinen Regel übereinstimmen. Der Name "Ausnahme" in der Informatik hat ebenfalls diese Bedeutung: Er impliziert, dass das Problem (die Ausnahme) nicht häufig auftritt, d. h. die Ausnahme ist die "Ausnahme von der Regel". Die Ausnahmebehandlung ist ein Konstrukt in einigen Programmiersprachen, um Fehler automatisch zu behandeln oder mit ihnen umzugehen. Viele Programmiersprachen wie C++, Objective-C, PHP, Java, Ruby, Python und viele andere haben eine eingebaute Unterstützung für die Ausnahmebehandlung.
Die Fehlerbehandlung wird im Allgemeinen dadurch gelöst, dass der Zustand der Ausführung zum Zeitpunkt des Auftretens des Fehlers gespeichert wird und der normale Programmfluss unterbrochen wird, um eine spezielle Funktion oder ein Stück Code auszuführen, das als Exception-Handler bezeichnet wird. Je nach Art des aufgetretenen Fehlers ("Division durch Null", "Datei-Öffnungsfehler" und so weiter) kann der Error-Handler das Problem "beheben" und das Programm kann anschließend mit den zuvor gespeicherten Daten fortgesetzt werden.
Ausnahmebehandlung in Python
Die Ausnahmebehandlung in Python ist der in Java sehr ähnlich. Der Code, der das Risiko einer Ausnahme birgt, wird in einen Try-Block eingebettet. Während in Java Ausnahmen durch catch-Klauseln abgefangen werden, haben wir in Python Anweisungen, die durch ein "except"-Schlüsselwort eingeleitet werden. Es ist möglich, "maßgeschneiderte" Ausnahmen zu erstellen: Mit der raise-Anweisung ist es möglich, das Auftreten einer bestimmten Ausnahme zu erzwingen.
Schauen wir uns ein einfaches Beispiel an. Angenommen, wir wollen den Benutzer auffordern, eine ganze Zahl einzugeben. Wenn wir ein input() verwenden, wird die Eingabe ein String sein, den wir in einen Integer umwandeln müssen. Wenn die Eingabe keine gültige Ganzzahl ist, wird ein ValueError erzeugt (raise). Wir zeigen dies in der folgenden interaktiven Sitzung:
n = int(input("Bitte gebe eine Zahl ein: "))
Mit Hilfe der Ausnahmebehandlung können wir robusten Code für das Lesen eines Integers von der Eingabe schreiben:
while True:
try:
n = input("Bitte gebe eine ganze Zahl ein: ")
n = int(n)
break
except ValueError:
print("Keine gültige Zahl, probiere es nochmals ...")
print("Großartig! Das war eine korrekte ganze Zahl")
Es handelt sich um eine Schleife, die nur dann abbricht, wenn eine gültige Ganzzahl angegeben wurde. Die while-Schleife wird eingegeben. Der Code innerhalb der try-Klausel wird Anweisung für Anweisung ausgeführt. Tritt während der Ausführung keine Exception auf, wird die Ausführung bis zur break-Anweisung geführt und die while-Schleife verlassen. Tritt eine Exception auf, z. B. beim Casting von n, wird der Rest des try-Blocks übersprungen und die except-Klausel ausgeführt. Der ausgelöste Fehler, in unserem Fall ein ValueError, muss mit einem der Namen hinter except übereinstimmen. In unserem Beispiel nur einer, nämlich "ValueError:". Nachdem der Text der print-Anweisung ausgegeben wurde, führt die Ausführung eine weitere Schleife aus. Sie beginnt mit einer neuen Input().
Mehrere Except-Klauseln
Eine try-Anweisung kann mehr als eine Except-Klausel für verschiedene Ausnahmen haben. Es wird aber höchstens eine Ausnahmeklausel ausgeführt.
Unser nächstes Beispiel zeigt eine try-Klausel, in der wir eine Datei zum Lesen öffnen, eine Zeile aus dieser Datei lesen und diese Zeile in eine Ganzzahl umwandeln. Es gibt mindestens zwei mögliche Ausnahmen:
Ein IOError
ValueError
Für den Fall, dass ein unerwarteter Fehler auftritt, haben wir zusätzlich eine unbenannte except-Klausel:
import sys
try:
f = open('integers.txt')
s = f.readline()
i = int(s.strip())
except IOError as e:
errno, strerror = e.args
print("I/O error({0}): {1}".format(errno,strerror))
# e can be printed directly without using .args:
# print(e)
except ValueError:
print("Keine gültige ganze Zahl.")
except:
print("Unerwarteter Fehler:", sys.exc_info()[0])
raise
Die Behandlung des IOError im vorherigen Beispiel ist von besonderem Interesse. Die except-Klausel für den IOError spezifiziert eine Variable "e" hinter dem Ausnahmenamen (IOError). Die Variable "e" ist an eine Exception-Instanz mit den in instance.args gespeicherten Argumenten gebunden. Wenn wir das obige Skript mit einer nicht existierenden Datei aufrufen, erhalten wir die Meldung:
IOError(2): No such file or directory
Und wenn die Datei integers.txt nicht lesbar ist, z. B. wenn wir nicht die Berechtigung haben, sie zu lesen, erhalten wir die folgende Meldung:
I/O error(13): Permission denied
Eine except-Klausel kann mehr als eine Ausnahme in einem Tupel von Fehlernamen nennen, wie wir im folgenden Beispiel sehen:
try:
f = open('integers.txt')
s = f.readline()
i = int(s.strip())
except (IOError, ValueError):
print("Keine gültige ganze Zahl.")
except:
print("Unerwarteter Fehler:", sys.exc_info()[0])
raise
Wir wollen nun demonstrieren, was passiert, wenn wir eine Funktion innerhalb eines Try-Blocks aufrufen und innerhalb des Funktionsaufrufs eine Exception auftritt:
def f():
x = int("four")
try:
f()
except ValueError as e:
print("Hab' den Fehler :-) ", e)
print("Weiter geht's")
die Funktion fängt die Ausnahme ab.
Wir werden unser Beispiel nun so erweitern, dass die Funktion die Exception direkt abfängt:
def f():
try:
x = int("four")
except ValueError as e:
print("Fehler in Funktion :-) ", e)
try:
f()
except ValueError as e:
print("got it :-) ", e)
print("Weiter geht's")
Wie wir erwartet haben, wird die Ausnahme innerhalb der Funktion abgefangen und nicht in der Ausnahme des Aufrufers:
Wir fügen nun ein "raise" hinzu, das den ValueError erneut erzeugt, so dass die Exception an den Aufrufer propagiert wird:
def f():
try:
x = int("four")
except ValueError as e:
print("Fehler in Funktion :-) ", e)
raise
try:
f()
except ValueError as e:
print("got it :-) ", e)
print("Weiter geht's")
raise SyntaxError("Sorry, mein Fehler!")
Der beste bzw. der pythonische Weg, dies zu tun, besteht darin, eine Ausnahmeklasse zu definieren, die von der Klasse Exception erbt. Sie müssen das Kapitel über Objektorientierte Programmierung durcharbeiten, um das folgende Beispiel vollständig zu verstehen:
class MyException(Exception):
pass
raise MyException("Eine Ausnahme bestätigt nicht die Regel!")
Aufräum-Aktionen (try ... finally)
Bislang wurde die try-Anweisung immer mit except-Klauseln gepaart. Es gibt aber auch eine andere Möglichkeit, sie zu verwenden. Die try-Anweisung kann von einer finally-Klausel gefolgt werden. Finally-Klauseln werden als Aufräum- oder Beendigungsklauseln bezeichnet, weil sie unter allen Umständen ausgeführt werden müssen, d. h. eine "finally"-Klausel wird immer ausgeführt, unabhängig davon, ob in einem try-Block eine Ausnahme aufgetreten ist oder nicht. Ein einfaches Beispiel zur Demonstration der finally-Klausel:
try:
x = float(input("Deine Zahl: "))
inverse = 1.0 / x
finally:
print("""Vielleicht ist eine Ausnahme
aufgetreten oder auch nicht.""")
print("Die inverse Zahl: ", inverse)
try:
x = float(input("Deine Zahl: "))
inverse = 1.0 / x
except ValueError:
print("Das war wohl kein Zahl!")
except ZeroDivisionError:
print("Unendlich")
finally:
print("""Vielleicht ist eine Ausnahme
aufgetreten oder auch nicht.""")
print("Die inverse Zahl: ", inverse)
else-Klausel
Die try ... except-Anweisung hat eine optionale else-Klausel. Ein else-Block muss nach allen except-Klauseln platziert werden. Eine else-Klausel wird ausgeführt, wenn die try-Klausel keine Ausnahme auslöst.
Das folgende Beispiel öffnet eine Datei und liest alle Zeilen in eine Liste namens "text" ein:
file_name = "trullala.txt"
text = []
try:
fh = open(file_name, 'r')
text = fh.readlines()
fh.close()
except IOError:
print('Datei lässt sich nicht öffnen:', file_name)
if text:
print(text[100])
Im folgenden Beispiel haben wir die beiden Zeilen nach dem open-Aufruf unterhalb des else-Blockes geschrieben. Wenn es mit dem Öffnen klappt, wird die Ausführung mit dem else-Block fortgesetzt. Erfolgt jedoch eine Ausnahme, wird der else-Block übersprungen:
import sys
file_name = "fibonacci.py"
text = []
try:
fh = open(file_name, 'r')
except IOError:
print('cannot open', file_name)
else:
text = fh.readlines()
fh.close()
if text:
print(text[10])
Die assert-Anweisung
Die assert-Anweisung ist für Debug-Aufgaben bestimmt: Sie kann als abgekürzte Schreibweise für eine bedingte raise-Anweisung angesehen werden, d.h. eine Ausnahme wird nur dann generiert, wenn eine bestimmte Bedingung nicht wahr ist.Ohne die assert-Anweisung zu benutzen würden wir dies wie folgt in Python formulieren:
if not <some_test>: raise AssertionError(<message>)Der folgende Code - unter Benutzung der assert-Anweisung - ist semantisch äquivalent, d.h. er hat die gleiche Bedeutung:
assert <some_test>, <message>Die obige Zeile kann wie folgt "gelesen" werden: Falls <some_test> als False ausgewertet wird, wird eine Ausnahme generiert und <message> wird ausgegeben.
Beispiel:
x = 5
y = 3
assert x < y, "x has to be smaller than y"
Hinweis: assert sollte nicht zum "Fangen" von Programmfehlern wie x / 0 benutzt werden, weil diese von Python selbst bestens erkannt und behandelt werden! assert sollte verwendet werden um bestimmte, vom Benutzer definierte, Einschränkungen zu "fangen".