In Python werden try und except verwendet, um Ausnahmen (Exceptions) zu behandeln (exception handling), d. h. Fehler, die während der Ausführung festgestellt werden. Mit try und except wird der Prozess auch dann fortgesetzt, wenn eine Ausnahme auftritt, ohne dass er beendet wird. Außerdem können else und finally verwendet werden, um das Ende des Prozesses festzulegen.
Basis Exception-Behandlung in Python: try … except …
Wenn zum Beispiel versucht wird, durch Null zu dividieren, wird ein ZeroDivisionError ausgelöst, der den Prozess beendet.
# print(1 / 0)
# ZeroDivisionError: division by zero
Die Exception fängt man mit folgendem Code ab:
try:
print(1 / 0)
except ZeroDivisionError:
print('Error')
Durch die Verwendung von „except <exception-name> as <variable-name>:“ wird das Exception-Objekt in der Variablen gespeichert. Man kann einen beliebigen Namen für die Variable wählen, aber Namen wie e, ex und err werden häufig verwendet.
Das Exception-Objekt enthält Fehlermeldungen, die beim Auftreten einer Exception angezeigt werden, so dass man die Fehlerdetails überprüfen kann, indem man sie ausgibt.
try:
print(1 / 0)
except ZeroDivisionError as e:
print(e)
print(type(e))
# division by zero
# <class 'ZeroDivisionError'>
Wenn eine Ausnahme in der try-Klausel auftritt, wird der nachfolgende Prozess in der try-Klausel übersprungen.
Wenn, wie im folgenden Beispiel gezeigt, in der Mitte der for-Schleife eine Exception auftritt, endet die Schleife an dieser Stelle und der Prozess in der except-Klausel wird ausgeführt.
try:
for i in [-2, -1, 0, 1, 2]:
print(1 / i)
except ZeroDivisionError as e:
print(e)
# -0.5
# -1.0
# division by zero
Man kann den Prozess, der nach der except-Klausel ausgeführt werden soll, mit den else- und finally-Klauseln angeben. Diese Verwendung wird später im Artikel noch beschrieben.
Mehrfache Exceptions abfangen
Ausgangspunkt ist eine Funktion, die den ZeroDivisionError abfängt.
def divide(a, b):
try:
print(a / b)
except ZeroDivisionError as e:
print('catch ZeroDivisionError:', e)
Mit dieser Funktion wird zwar ZeroDivisionError abgefangen, aber keine andere Exception.
divide(1, 0)
# catch ZeroDivisionError: division by zero
# divide('a', 'b')
# TypeError: unsupported operand type(s) for /: 'str' and 'str'
verschiedene Operationen auf mehrere Exceptions anwenden
Man kann mehrere Ausnahmeklauseln angeben und für jede Exception unterschiedliche Operationen zuweisen.
def divide_each(a, b):
try:
print(a / b)
except ZeroDivisionError as e:
print('catch ZeroDivisionError:', e)
except TypeError as e:
print('catch TypeError:', e)
divide_each(1, 0)
# catch ZeroDivisionError: division by zero
divide_each('a', 'b')
# catch TypeError: unsupported operand type(s) for /: 'str' and 'str'
die gleiche Operation auf mehrere Exceptions anwenden
Man kann mehrere Exceptions als Tupel in einer einzigen except-Klausel angeben.
def divide_same(a, b):
try:
print(a / b)
except (ZeroDivisionError, TypeError) as e:
print(e)
divide_same(1, 0)
# division by zero
divide_same('a', 'b')
# unsupported operand type(s) for /: 'str' and 'str'
Alle Exceptions abfangen
Es ist auch möglich, alle Ausnahmen abzufangen, ohne sie zu spezifizieren.
Wildcard except (bare except)
Alle Ausnahmen können abgefangen werden, indem der Name der Exception in der except-Klausel weggelassen wird. Wenn es mehrere except-Klauseln gibt, kann der Exception-Name nur in der letzten except-Klausel weggelassen werden.
Die except-Klausel ohne Exception-Namen wird als wildcard except, bare except, etc. bezeichnet. Man sollte sie mit Vorsicht verwenden, wie in der offiziellen Dokumentation erwähnt.
The last except clause may omit the exception name(s), to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way!
8. Errors and Exceptions – Handling Exceptions — Python 3.9.0 documentation
def divide_wildcard(a, b):
try:
print(a / b)
except:
print('Error')
divide_wildcard(1, 0)
# Error
divide_wildcard('a', 'b')
# Error
Mit einem Wildcard except werden alle Ausnahmen abgefangen, einschließlich SystemExit (ausgelöst durch sys.exit() usw.) und KeyboardInterrupt (ausgelöst, wenn die Unterbrechungstaste Ctrl + C gedrückt wird).
In vielen Fällen ist es besser, diese Ausnahmen nicht abzufangen, so dass die Verwendung von Exception, wie im Folgenden beschrieben, eine bessere Option ist.
Basis-Klasse: Exception
Man kann in der except-Klausel Exception angeben, die die Basisklasse für alle eingebauten (built-in), nicht systembedingten Ausnahmen ist.
def divide_exception(a, b):
try:
print(a / b)
except Exception as e:
print(e)
divide_exception(1, 0)
# division by zero
divide_exception('a', 'b')
# unsupported operand type(s) for /: 'str' and 'str'
Die Klassenhierarchie für built-in Exceptions sieht folgendermaßen aus.
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ...
...
Da SystemExit und KeyboardInterrupt nicht von Exception erben, wird die Angabe von Exception in der except-Klausel sys.exit() und die Ausnahme der Interrupt-Tasteneingabe nicht abfangen.
Die Basisklasse für alle eingebauten Ausnahmen, einschließlich SystemExit und KeyboardInterrupt, ist BaseException. Wenn man BaseException anstelle von Exception in der except-Klausel angibt, werden alle Ausnahmen abgefangen, auch die Wildcard-Ausnahmen.
Es ist besser, die erwarteten Ausnahmen so weit wie möglich in der except-Klausel zu spezifizieren, da das Abfangen selbst einer unerwarteten Ausnahme einen Fehler verursachen kann.
Weitere Einzelheiten sind in der offiziellen Python-Dokumentation beschrieben.
Aktion ausführen, wenn keine Exception auftritt: try … except … else …
Man kann in der else-Klausel die Aktion angeben, die ausgeführt werden soll, wenn keine Exception auftritt. Wenn eine Ausnahme auftritt und von except abgefangen wird, wird die Aktion in der else-Klausel nicht ausgeführt!
def divide_else(a, b):
try:
print(a / b)
except ZeroDivisionError as e:
print('catch ZeroDivisionError:', e)
else:
print('finish (no error)')
divide_else(1, 2)
# 0.5
# finish (no error)
divide_else(1, 0)
# catch ZeroDivisionError: division by zero
Aufräum- oder Abschlussaktion: try … except … finally …
In der finally-Klausel kann man die Aufräum- bzw. Abschlussaktion angeben, die ausgeführt werden soll, unabhängig davon, ob eine Exception auftritt oder nicht.
def divide_finally(a, b):
try:
print(a / b)
except ZeroDivisionError as e:
print('catch ZeroDivisionError:', e)
finally:
print('all finish')
divide_finally(1, 2)
# 0.5
# all finish
divide_finally(1, 0)
# catch ZeroDivisionError: division by zero
# all finish
Man kann auch die else- und die finally-Klausel zusammen verwenden. Wenn keine Exception auftritt, wird die else-Klausel ausgeführt und dann die finally-Klausel.
def divide_else_finally(a, b):
try:
print(a / b)
except ZeroDivisionError as e:
print(‚catch ZeroDivisionError:‘, e)
else:
print(‚finish (no error)‘)
finally:
print(‚all finish‘)
divide_else_finally(1, 2)
# 0.5
# finish (no error)
# all finish
divide_else_finally(1, 0)
# catch ZeroDivisionError: division by zero
# all finish
Exceptions ignorieren
Wenn man eine Exception abfangen und weiterleiten möchten, ohne etwas zu tun, verwendet man den Befehl pass.
def divide_pass(a, b):
try:
print(a / b)
except ZeroDivisionError:
pass
divide_pass(1, 0)
Praktisches Beispiel: Bilddateien lesen
Ein praktisches Beispiel für die Verwendung des Exception Handling (Ausnahmebehandlung) ist das Lesen von Bilddateien.
Es folgt ein Beispiel für die Größenänderung der Bilddateien in einem Ordner unter der Verwendung mit Pillow.
Ohne Exception Handling
Alle Dateipfade im Ordner mit glob() ermitteln und nur die Größe von Dateien ändern, die mit bestimmten Erweiterungen übereinstimmen.
import os
import glob
from PIL import Image
dst_dir = ‚data/temp/images_half‘
os.makedirs(dst_dir, exist_ok=True)
files = glob.glob(‚./data/temp/images/*‘)
for f in files:
root, ext = os.path.splitext(f)
if ext in [‚.jpg‘, ‚.png‘]:
img = Image.open(f)
img_resize = img.resize((img.width // 2, img.height // 2))
basename = os.path.basename(root)
img_resize.save(os.path.join(dst_dir, basename + ‚_half‘ + ext))
Da Bilddateien verschiedene Erweiterungen haben können, ist es schwierig, sie alle anzugeben.
Mit Exception Handling
files = glob.glob(‚./data/temp/images/*‘)
for f in files:
try:
img = Image.open(f)
img_resize = img.resize((img.width // 2, img.height // 2))
root, ext = os.path.splitext(f)
basename = os.path.basename(root)
img_resize.save(os.path.join(dst_dir, basename + ‚_half‘ + ext))
except OSError as e:
pass
Alle Dateien, die mit Pillow’s Image.open() geöffnet werden können, werden in der Größe angepasst, alle andere Dateien werden übergangen.
Der Ansatz, der die Bedingungen explizit prüft, wie im ersten Beispiel, wird „LBYL: Look Before You Leap“ („Erst schauen, dann handeln“) genannt, während der Ansatz, der eine Ausnahmebehandlung verwendet, wie im zweiten Beispiel, als „EAFP: Easier to Ask for Forgiveness than Permission“ („Es ist einfacher, um Verzeihung zu bitten als um Erlaubnis“) bezeichnet wird.
Beide haben Vor- und Nachteile, aber der Prozess, der viele Bedingungen benötigt, kann durch die Verwendung der Ausnahmebehandlung übersichtlicher geschrieben werden.