(c) 2023 Technische Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever - Impressum
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
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:
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
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.
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.
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'
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 ?
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
Die try-Anweisung funktioniert wie folgt:
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:
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
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:
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:
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:
%%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
!python testOSError.py myfile.txt
cannot open myfile.txt
%%writefile myfile.txt
Dies ist ein Testfile
mit Text in
mehreren Zeilen
Writing myfile.txt
!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.
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:
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
Die raise-Anweisung erlaubt dem Programmierer, eine bestimmte Ausnahme zu erzwingen. Beispielsweise:
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:
raise ValueError
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-18-e4c8e09828d5> in <module>() ----> 1 raise ValueError ValueError:
steht kurz für
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:
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
%%Mooc MoocStringAssessment
Es ist folgender Code gegeben:
try:
a = 7/0
except KeyboardInterrupt:
print("Unterbrechung")
except:
print("anderer Fehler")
%%Mooc MoocStringAssessment
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())
%%Mooc MoocMultipleChoiceAssessment
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))
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:
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.
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:
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:
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.
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:
for line in open("myfile.txt"):
print(line, end="")
Dies ist ein Testfile mit Text in mehreren Zeilen
fd = open("myfile.txt")
for line in open("myfile.txt"):
print(line, end="")
fd.closed
Dies ist ein Testfile mit Text in mehreren Zeilen
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:
with open("myfile.txt") as fdw:
for line in fdw:
print(line, end="")
fdw.closed
Dies ist ein Testfile mit Text in mehreren Zeilen
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.
%%Mooc Video
%%Mooc WebReference