Wir haben schon mehrfach festgestellt, dass beim Ausführen von Programmen Fehler aufgetreten sind, die zum Abbruch des Programms geführt haben. Das passiert beispielsweise, wenn wir auf ein nicht existierendes Element einer Liste zuzugreifen versuchen:
In [ ]:
names = ['Otto', 'Hugo', 'Maria']
names[3]
Oder wenn wir versuchen eine Zahl durch 0 zu dividieren:
In [ ]:
user_input = 0
2335 / user_input
Oder wenn wir versuchen, eine nicht vorhandene Datei zu öffnen:
In [ ]:
with open('hudriwudri.txt') as fh:
print(fh.read())
Wenn Python auf ein Problem stößt, erzeugt es ein Ausnahme-Objekt und beendet das Programm. Bei Bedarf haben wir allerdings die Möglichkeit, auf eine solche Ausnahme anders als mit einem Programmabbruch zu reagieren.
Dazu müssen wir den Code, der zu einer Ausnahme führen kann, in ein try ... except
Konstrukt einbetten. Der fehleranfällige Codeteil steht im try
-Block, gefolgt von einem except
-Block, in dem steht, wie das Programm auf einen allfälligen Fehler reagieren soll.
In [ ]:
divisor = int(input('Divisor: '))
try:
print(6543 / divisor)
except:
print('Bei der Division ist ein Fehler aufgetreten')
Was wir hier gemacht haben, ist jedoch ganz schlechter Stil: Wir haben jede Art von Fehler oder Warnung abgefangen. Damit werden unter Umständen auch Fehler abgefangen, die wir gar nicht abfangen wollten, und die unter Umständen entscheidende Hinweise zur Fehlersuche geben könnten. Hier ein sehr konstruiertes Beispiel um das zu verdeutlichen:
In [ ]:
divisor = int(input('Divisor: '))
some_names = ['Otto', 'Anna']
try:
print(some_names[divisor])
print(6543 / divisor)
except:
print('Bei der Division ist ein Fehler aufgetreten')
Hier noch einmal die drei Fragmente, mit denen wir am Anfang dieses Notebooks Fehler ausgelöst haben:
In [ ]:
names = ['Otto', 'Hugo', 'Maria']
names[3]
In [ ]:
user_input = 0
2335 / user_input
In [ ]:
with open('hudriwudri.txt') as fh:
print(fh.read())
Wenn wir genau hinsehen, stellen wir fest, dass es sich um drei unterschiedliche Arten von Fehlern handelt:
names[3]
)2335 / user_input
)open('hudriwudri.txt')
)Python generiert also, abhängig davon, welche Art von Fehler aufgetreten ist, ein entsprechendes Ausnahme-Objekt. Alle diese Fehler sind abgeleitet vom allgemeinsten Ausnahme-Objekt (Exception
) und damit Spezialfälle dieser Ausnahme. Diese speziellen Ausnahme-Objekte haben den Vorteil, dass wir abhängig vom Fehlertyp unterschiedlich darauf reagieren können. Wenn wir nur die Division durch 0 abfangen wollen, sieht der Code so aus:
In [ ]:
divisor = int(input('Divisor: '))
try:
print(6543 / divisor)
except ZeroDivisionError:
print('Division durch 0 ist nicht erlaubt.')
Wenn wir hingegen eine Nicht-Zahl (z.B. 'abc') eingeben, wird das Programm nach wie vor abgebrochen, weil hier ein ValueError
ausgelöst wird, den wir nicht explizit abfangen.
In [ ]:
print(6543 / divisor)
try:
divisor = int(input('Divisor: '))
except (ZeroDivisionError, ValueError):
print('Bei der Division ist ein Fehler aufgetreten.')
Überlegen Sie, warum das Programm immer noch abgebrochen wird und versuchen Sie, das Problem zu lösen!
Wir können alternativ auch unterschiedliche except-Blöcke verwenden um auf unterschiedliche Fehler unterschiedlich zu reagieren:
In [ ]:
try:
divisor = int(input('Divisor: '))
print(6543 / divisor)
except ZeroDivisionError:
print('Division durch 0 ist nicht erlaubt.')
except ValueError:
print('Sie müssen eine Zahl eingeben!')
Falls wir ein Code-Fragment in jedem Fall ausführen wollen, also unabhängig davon, ob ein Fehler aufgetreten ist oder nicht, können wir einen finally
-Block definieren. Da macht zum Beispiel Sinn, wenn wir irgendwelche Ressourcen wie Filehandles freigeben wollen.
try:
f = open('data.txt')
# make some computations on data
except ZeroDivisionError:
print('Warning: Division by Zero in data.txt')
finally:
f.close()
Wir haben oben schon festgestellt, dass bestimmte Ausnahmen Spezialfälle von anderen Ausnahmen sind. Die allgemeinste Ausnahme ist Exception
, von der alle anderen Ausnahmen abgeleitet sind. Ein ZeroDivisionError
ist ein Spezialfall von ArithmethicError
, was wiederum eine Spezialfall von Exception
ist.
Diese Hierarchie von Ausnahmen können wir so sichtbar machen (das dient nur zur Illustration und muss nicht verstanden werden):
In [ ]:
import inspect
inspect.getmro(ZeroDivisionError)
Wir können uns diese Hierarchie zunutze machen, um gleichartige Fehler gemeinsam zu behandeln. Wollen wir alle ArithmeticError-Subtypen (OverflowError, ZeroDivisionError, FloatingPointError) gesammelt abfangen wollen, prüfen wir beim except
auf diesen gemeinsamen Basistyp:
In [ ]:
try:
divisor = int(input('Divisor: '))
print(6543 / divisor)
except ArithmeticError:
print('Kann mit der eingegebenen Zahl nicht rechnen.')
Die komplette Hierarchie der eingebauten Exceptions findet sich hier: https://docs.python.org/3/library/exceptions.html#exception-hierarchy
In [ ]:
def ask_for_int(msg):
divisor = input('{}: '.format(msg))
return int(divisor)
try:
print(6543 / ask_for_int('Divisor eingeben'))
except ValueError:
print('Ich kann nur durch eine Zahl dividieren!')
Im obigen Code tritt (wenn wir z.B. 'abc' eingeben) der ValueError in der Funktion ask_for_int()
auf, wird aber im Hauptprogramm abgefangen. Das kann den Code unnötig komplex machen, weil wir beim Lesen des Codes immer feststellen müssen, wo der Fehler überhaupt herkommt, ermöglicht aber andererseits ein zentrales Ausnahme-Management.
In [ ]:
def ask_for_int(msg):
divisor = input('{}: '.format(msg))
return int(divisor)
try:
print(6543 / ask_for_int('Divisor eingeben'))
except ValueError as e:
print('Ich kann nur durch eine Zahl dividieren!')
print('Das Problem war: {}'.format(e.args))
In [ ]:
def ask_for_divisor():
divisor = input('Divisor eingeben: ')
if divisor == '0':
raise ValueError('Divisor must not be 0!')
return int(divisor)
try:
print(6543 / ask_for_divisor())
except ValueError:
print('Ungültige Eingabe')
In [ ]:
class MyAppException(Exception): pass
class MyAppWarning(MyAppException): pass
class MyAppError(MyAppException): pass
class GradeValueException(MyAppError): pass
Damit haben wir unsere eigene Ausnahmehierarchie definiert:
Exception
|---- MyAppException
| ---- MyAppWarning
| ---- MyAppError
| ---- GradeValueException
Je nach Bedarf können wir hier nun z.B. auf alle Subtypen von MyAppException
reagieren oder auf alle MyAppWarnings
oder MyAppErrors
oder ganz gezielt auf eine GradeValueException
.