Python & AI Tutorials Logo
Python Programmierung

25. Ausnahmen (Exceptions) souverän behandeln

In Kapitel 24 haben wir gelernt, wie man Ausnahmen (exceptions) liest und versteht, wenn sie auftreten. Jetzt lernen wir, wie wir Ausnahmen behandeln können, sodass sich unsere Programme von Fehlern erholen, statt abzustürzen. Das ist entscheidend, um robuste, benutzerfreundliche Programme zu schreiben, die mit unerwarteten Situationen umgehen können.

Wenn in Python eine Ausnahme auftritt, stoppt der normale Ablauf des Programms sofort. Aber was wäre, wenn wir diese Ausnahme abfangen könnten, bevor sie unser Programm zum Absturz bringt? Was wäre, wenn wir auf den Fehler reagieren könnten, vielleicht indem wir den Benutzer bitten, es erneut zu versuchen, oder indem wir einen Standardwert verwenden, oder indem wir das Problem protokollieren und fortfahren? Genau das ermöglicht uns die Ausnahmebehandlung.

25.1) try- und except-Blöcke verwenden

25.1.1) Die Grundstruktur von try und except

Ein try-except-Block ist Pythons Art zu sagen: „Versuche das zu tun, und wenn eine Ausnahme auftritt, mache stattdessen das.“ Die Grundstruktur sieht so aus:

python
try:
    # Code, der eine Ausnahme auslösen könnte
    risky_operation()
except:
    # Code, der ausgeführt wird, wenn IRGENDEINE Ausnahme auftritt
    print("Something went wrong!")

Der try-Block enthält Code, der eine Ausnahme auslösen könnte. Wenn irgendwo im try-Block eine Ausnahme auftritt, stoppt Python sofort die Ausführung des try-Blocks und springt in den except-Block. Tritt keine Ausnahme auf, wird der except-Block vollständig übersprungen.

Schauen wir uns ein konkretes Beispiel an. Erinnern Sie sich aus Kapitel 24 daran, dass der Versuch, einen ungültigen String in eine Ganzzahl umzuwandeln, eine ValueError auslöst:

python
# Ohne Ausnahmebehandlung – Programm stürzt ab
user_input = "hello"
number = int(user_input)  # ValueError: invalid literal for int() with base 10: 'hello'
print("This line never executes")

Jetzt behandeln wir diese Ausnahme souverän:

python
# Mit Ausnahmebehandlung – Programm läuft weiter
user_input = "hello"
 
try:
    number = int(user_input)
    print(f"You entered: {number}")
except:
    print("That's not a valid number!")
    number = 0  # Einen Standardwert verwenden
 
print(f"Using number: {number}")

Output:

That's not a valid number!
Using number: 0

Das Programm ist nicht abgestürzt! Als int(user_input) einen ValueError auslöste, sprang Python in den except-Block, gab unsere Fehlermeldung aus, setzte einen Standardwert und fuhr dann mit dem restlichen Programm fort.

Das passiert Schritt für Schritt:

Nein

Ja

try-Block starten

int-Umwandlung ausführen

Ausnahme ausgelöst?

Im try-Block fortfahren

Zum except-Block springen

except-Block überspringen

except-Code ausführen

Nach try-except fortfahren

Den „Sprung“ verstehen – was wirklich passiert

Wenn wir sagen, Python „springt“ in den except-Block, meinen wir, dass es die normale sequenzielle Ausführung aufgibt. Das ist eine grundlegende Änderung darin, wie Ihr Programm abläuft – nicht nur ein einfacher Zweig wie bei einer if-Anweisung. Sehen wir uns das im Detail mit einem konkreten Beispiel an:

python
# Ausführungsfluss mit Ausnahmen beobachten
print("1. Starting program")
 
try:
    print("2. Entered try block")
    number = int("hello")  # Ausnahme passiert HIER
    print("3. After conversion")   # Diese Zeile wird NIE ausgeführt
    result = number * 2            # Diese Zeile wird NIE ausgeführt
    print("4. After calculation")  # Diese Zeile wird NIE ausgeführt
except ValueError:
    print("5. In except block - handling the error")
 
print("6. After try-except block")

Output:

1. Starting program
2. Entered try block
5. In except block - handling the error
6. After try-except block

Beachten Sie, dass die Zeilen 3 und 4 nie ausgeführt werden! In dem Moment, in dem int("hello") einen ValueError auslöst, macht Python Folgendes:

  1. Stoppt die Ausführung des try-Blocks sofort – genau in der Zeile, in der die Ausnahme auftrat
  2. Sucht nach einer passenden except-Klausel, die diesen Ausnahmetyp behandeln kann
  3. Springt in diesen except-Block und überspringt den restlichen Code im try-Block
  4. Fährt fort nach der try-except-Struktur, sobald der except-Block abgeschlossen ist

Das unterscheidet sich grundlegend vom normalen Programmablauf. Bei normaler Ausführung führt Python jede Zeile nacheinander aus. Bei einer Ausnahme verlässt Python den aktuellen Pfad und nimmt eine völlig andere Route. Ohne Ausnahmebehandlung würde das Programm in Zeile 2 abstürzen und beendet werden. Mit Ausnahmebehandlung erholt sich das Programm und läuft weiter.

Warum das wichtig ist:

Dieses „Sprung“-Verhalten zu verstehen, ist entscheidend, weil:

  • Jeglicher Code nach der Ausnahme im try-Block wird übersprungen – Sie können nicht davon ausgehen, dass spätere Zeilen im try-Block ausgeführt wurden
  • Variablen sind möglicherweise nicht initialisiert, wenn die Ausnahme vor ihrer Zuweisung auftritt
  • Sie müssen planen, in welchem Zustand Ihr Programm ist, wenn der except-Block ausgeführt wird

25.1.2) Benutzereingaben sicher behandeln

Eine der häufigsten Anwendungen der Ausnahmebehandlung ist die Validierung von Benutzereingaben. Benutzer können alles Mögliche eintippen, und wir müssen ungültige Eingaben souverän behandeln. Hier ist ein praktisches Beispiel für ein Programm, das nach dem Alter eines Benutzers fragt:

python
# Sichere Alterseingabe mit Ausnahmebehandlung
print("Please enter your age:")
user_input = input()
 
try:
    age = int(user_input)
    print(f"You are {age} years old.")
    
    # Geburtsjahr berechnen (angenommen, das aktuelle Jahr ist 2024)
    birth_year = 2024 - age
    print(f"You were born around {birth_year}.")
except:
    print("Invalid input! Age must be a number.")
    print("Using default age of 0.")
    age = 0

Wenn der Benutzer „25“ eingibt, ist die Ausgabe:

Please enter your age:
25
You are 25 years old.
You were born around 1999.

Wenn der Benutzer „twenty-five“ eingibt, ist die Ausgabe:

Please enter your age:
twenty-five
Invalid input! Age must be a number.
Using default age of 0.

Beachten Sie, wie das Programm den Fehler souverän behandelt, statt mit einem Traceback abzustürzen. Das ist viel besser für die Benutzererfahrung.

25.1.3) Mehrere Operationen in einem try-Block behandeln

Sie können mehrere Operationen in einen einzelnen try-Block setzen. Wenn eine davon eine Ausnahme auslöst, springt Python sofort in den except-Block. Beginnen wir mit einem einfachen Beispiel:

python
# Zwei Operationen im try-Block
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)      # Erste Operation – könnte ValueError auslösen
    result = 100 / number          # Zweite Operation – könnte ZeroDivisionError auslösen
    print(f"100 / {number} = {result}")
except:
    print("Something went wrong!")

Wenn der Benutzer „hello“ eingibt, tritt die Ausnahme bei der ersten Operation (Umwandlung) auf. Wenn der Benutzer „0“ eingibt, tritt die Ausnahme bei der zweiten Operation (Division) auf. In beiden Fällen fängt unser einzelner except-Block sie ab.

Jetzt erweitern wir das auf drei Operationen:

python
# Mehrere Operationen im try-Block
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
 
try:
    numerator = int(numerator_input)      # Könnte ValueError auslösen
    denominator = int(denominator_input)  # Könnte ValueError auslösen
    result = numerator / denominator      # Könnte ZeroDivisionError auslösen
    print(f"{numerator} / {denominator} = {result}")
except:
    print("Something went wrong with the calculation!")
    print("Make sure you enter valid numbers and don't divide by zero.")

Wenn der Benutzer „10“ und „2“ eingibt:

Enter two numbers to divide:
Numerator: 10
Denominator: 2
10 / 2 = 5.0

Wenn der Benutzer „10“ und „zero“ eingibt:

Enter two numbers to divide:
Numerator: 10
Denominator: zero
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.

Wenn der Benutzer „10“ und „0“ eingibt:

Enter two numbers to divide:
Numerator: 10
Denominator: 0
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.

In diesem Beispiel könnten drei verschiedene Dinge schiefgehen: Die Umwandlung des Zählers könnte fehlschlagen, die Umwandlung des Nenners könnte fehlschlagen oder die Division könnte fehlschlagen (wenn der Nenner null ist). Unser einzelner except-Block fängt alle diese Fälle ab. Allerdings hat dieser Ansatz eine Einschränkung: Wir können nicht erkennen, welcher konkrete Fehler aufgetreten ist. Darum kümmern wir uns im nächsten Abschnitt.

25.1.4) Das Problem mit nackten except-Klauseln

Die Verwendung von except: ohne Angabe eines Ausnahmetyps nennt man eine nackte except-Klausel (bare except clause). Sie fängt zwar alle Ausnahmen ab, ist aber oft zu breit und kann unerwartete Probleme verbergen. Betrachten Sie dieses Beispiel:

python
# Nacktes except fängt ALLES ab – sogar Dinge, die wir nicht erwarten
numbers = [10, 20, 30]
 
try:
    index = 5  # Wir erwarten IndexError, wenn index außerhalb des Bereichs liegt
    value = numbers[index]
    print(f"Value at index {index}: {value}")
except:
    print("Could not access the list element.")

Das wirkt sinnvoll – wir versuchen, auf ein Listenelement zuzugreifen, das möglicherweise nicht existiert. Aber was ist, wenn ein Tippfehler in unserem Code ist?

python
# Was, wenn ein Tippfehler in unserem Code ist?
numbers = [10, 20, 30]
 
try:
    index = 2
    value = numbrs[index]  # Tippfehler: 'numbrs' statt 'numbers'
    print(f"Value at index {index}: {value}")
except:
    print("Could not access the list element.")

Output:

Could not access the list element.

Das nackte except fängt den NameError vom Tippfehler ab und gibt „Could not access the list element“ aus – und liefert uns damit die falsche Information darüber, was schiefgelaufen ist! Wir denken, der Index sei außerhalb des Bereichs, aber tatsächlich haben wir einen Tippfehler im Variablennamen.

Ein nacktes except fängt außerdem KeyboardInterrupt (wenn der Benutzer Strg+C drückt) und SystemExit (wenn das Programm versucht zu beenden) ab, was normalerweise nicht abgefangen werden sollte. Aus diesen Gründen ist es besser, spezifische Ausnahmen abzufangen, was wir als Nächstes lernen.

25.2) Spezifische Ausnahmen abfangen

25.2.1) Ausnahmetypen angeben

Statt alle Ausnahmen mit einem nackten except abzufangen, können wir angeben, welche Ausnahmetypen wir behandeln wollen. Das macht unseren Code präziser und hilft uns, angemessen auf unterschiedliche Fehler zu reagieren:

python
# Einen spezifischen Ausnahmetyp abfangen
user_input = "hello"
 
try:
    number = int(user_input)
    print(f"You entered: {number}")
except ValueError:
    print("That's not a valid number!")
    number = 0
 
print(f"Using number: {number}")

Output:

That's not a valid number!
Using number: 0

Jetzt fängt unsere except-Klausel nur ValueError-Ausnahmen ab. Wenn eine andere Art von Ausnahme auftritt (wie ein NameError durch einen Tippfehler), wird sie nicht abgefangen, und wir sehen den vollständigen Traceback – was beim Debugging tatsächlich hilfreich ist!

Die Syntax ist: except ExceptionType: wobei ExceptionType der Name der Ausnahme-Klasse ist, die Sie abfangen möchten (wie ValueError, TypeError, ZeroDivisionError usw.).

Häufiger Fehler: Den falschen Ausnahmetyp abfangen

Was passiert, wenn Sie einen Ausnahmetyp angeben, der nicht zu dem passt, was tatsächlich auftritt? Schauen wir uns das an:

python
# Den falschen Ausnahmetyp abfangen
user_input = "hello"
 
try:
    number = int(user_input)  # Das löst ValueError aus
    print(f"You entered: {number}")
except TypeError:  # Aber wir fangen TypeError ab!
    print("That's not a valid number!")
    number = 0
 
print(f"Using number: {number}")

Output:

Traceback (most recent call last):
  File "example.py", line 4, in <module>
    number = int(user_input)
ValueError: invalid literal for int() with base 10: 'hello'

Das Programm ist abgestürzt! Warum? Weil int("hello") einen ValueError auslöst, aber unsere except-Klausel nur TypeError abfängt. Da es keine passende except-Klausel gibt, wird die Ausnahme nicht abgefangen und das Programm beendet sich.

Das ist während der Entwicklung tatsächlich hilfreich – wenn Sie den falschen Ausnahmetyp abfangen, sehen Sie den vollständigen Traceback und erkennen Ihren Fehler. Das ist ein Grund, warum das Abfangen spezifischer Ausnahmen besser ist als ein nacktes except.

So vermeiden Sie diesen Fehler:

  1. Lesen Sie den Traceback, um zu sehen, welcher Ausnahmetyp tatsächlich aufgetreten ist
  2. Verwenden Sie diesen spezifischen Ausnahmetyp in Ihrer except-Klausel
  3. Wenn Sie unsicher sind, lassen Sie den Code laufen und abstürzen – der Traceback wird es Ihnen sagen!

25.2.2) Unterschiedliche Ausnahmen unterschiedlich behandeln

Sie können mehrere except-Klauseln haben, um unterschiedliche Ausnahmetypen auf unterschiedliche Weise zu behandeln. Das ist extrem nützlich, wenn verschiedene Fehler unterschiedliche Reaktionen erfordern:

python
# Unterschiedliche Behandlung für unterschiedliche Ausnahmen
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
 
try:
    numerator = int(numerator_input)
    denominator = int(denominator_input)
    result = numerator / denominator
    print(f"{numerator} / {denominator} = {result}")
except ValueError:
    print("Error: Both inputs must be valid integers.")
    print("You entered something that isn't a number.")
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")
    print("The denominator must be a non-zero number.")

Wenn der Benutzer „10“ und „abc“ eingibt:

Enter two numbers to divide:
Numerator: 10
Denominator: abc
Error: Both inputs must be valid integers.
You entered something that isn't a number.

Wenn der Benutzer „10“ und „0“ eingibt:

Enter two numbers to divide:
Numerator: 10
Denominator: 0
Error: Cannot divide by zero.
The denominator must be a non-zero number.

Python prüft jede except-Klausel der Reihe nach. Wenn eine Ausnahme auftritt, findet Python die erste except-Klausel, die zum Ausnahmetyp passt, und führt diesen Block aus. Die anderen except-Klauseln werden übersprungen.

Ja

Nein

Ja

Nein

Ja

Nein

Ausnahme im try-Block ausgelöst

Passt zur ersten except-Klausel?

Ersten except-Block ausführen

Passt zur zweiten except-Klausel?

Zweiten except-Block ausführen

Passt zur dritten except-Klausel?

Dritten except-Block ausführen

Ausnahme nicht abgefangen – propagiert nach oben

Nach try-except fortfahren

25.2.3) Mehrere Ausnahmetypen in einer Klausel abfangen

Manchmal möchten Sie mehrere verschiedene Ausnahmetypen auf die gleiche Weise behandeln. Statt mehrere identische except-Blöcke zu schreiben, können Sie mehrere Ausnahmetypen in einer einzigen Klausel abfangen, indem Sie sie in Klammern als Tuple angeben:

python
# Mehrere Ausnahmetypen zusammen abfangen
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
    result = 100 / number
    print(f"100 divided by {number} is {result}")
except (ValueError, ZeroDivisionError):
    print("Invalid input or division by zero.")
    print("Please enter a non-zero number.")

Wenn der Benutzer „hello“ eingibt:

Enter a number:
hello
Invalid input or division by zero.
Please enter a non-zero number.

Wenn der Benutzer „0“ eingibt:

Enter a number:
0
Invalid input or division by zero.
Please enter a non-zero number.

Sowohl ValueError (durch ungültige Umwandlung) als auch ZeroDivisionError (durch Division durch Null) werden durch dieselbe except-Klausel behandelt. Das ist nützlich, wenn unterschiedliche Fehler dieselbe Reaktion auslösen sollen.

25.2.4) Auf Ausnahmeinformationen zugreifen

Manchmal müssen Sie mehr Details über die aufgetretene Ausnahme wissen. Sie können das Ausnahmeobjekt mit dem Schlüsselwort as abfangen. Aber zuerst sollten wir verstehen, was ein Ausnahmeobjekt überhaupt ist.

Was ist ein Ausnahmeobjekt?

Wenn Python eine Ausnahme auslöst, signalisiert es nicht nur, dass etwas schiefgelaufen ist – es erstellt ein Objekt, das Informationen über den Fehler enthält. Dieses Ausnahmeobjekt ist wie ein detaillierter Fehlerbericht, der Folgendes enthält:

  • Die Fehlermeldung: Eine Beschreibung dessen, was schiefgelaufen ist
  • Den Ausnahmetyp: Welche Art von Fehler aufgetreten ist (ValueError, TypeError, usw.)
  • Zusätzliche Attribute: Spezifische Informationen abhängig vom Ausnahmetyp

Stellen Sie sich ein Ausnahmeobjekt als Container vor, der alle Informationen über einen Fehler enthält. So wie ein Listenobjekt Elemente enthält und Methoden wie append() hat, enthält ein Ausnahmeobjekt Fehlerinformationen und hat Attribute, auf die Sie zugreifen können.

Wenn Sie except ValueError as error: schreiben, sagen Sie Python: „Wenn ein ValueError auftritt, erstelle eine Variable namens error und lege das Ausnahmeobjekt hinein, damit ich es untersuchen kann.“

Lassen Sie uns erkunden, was in einem Ausnahmeobjekt steckt:

python
# Inhalte des Ausnahmeobjekts untersuchen
try:
    number = int("hello")
except ValueError as error:
    print("Exception caught! Let's examine it:")
    print(f"Type: {type(error)}")
    print(f"String representation: {error}")
    print(f"Args tuple: {error.args}")

Output:

Exception caught! Let's examine it:
Type: <class 'ValueError'>
String representation: invalid literal for int() with base 10: 'hello'
Args tuple: ("invalid literal for int() with base 10: 'hello'",)

Das Ausnahmeobjekt hat:

  • Einen Typ (ValueError-Klasse) – das sagt Ihnen, welche Art von Fehler aufgetreten ist
  • Eine String-Repräsentation (die Fehlermeldung) – das ist, was Sie in Tracebacks sehen
  • Ein args-Attribut (Tuple, das die Meldung und ggf. andere Argumente enthält) – das liefert strukturierten Zugriff auf Fehlerdetails

Warum das wichtig ist:

Verschiedene Ausnahmetypen haben unterschiedliche Attribute, die spezifische Informationen liefern. Wenn Sie die Struktur von Ausnahmeobjekten verstehen, können Sie nützliche Informationen für Debugging oder Benutzerfeedback extrahieren:

python
# Unterschiedliche Ausnahmen haben unterschiedliche Attribute
numbers = [10, 20, 30]
 
try:
    value = numbers[10]
except IndexError as error:
    print(f"IndexError message: {error}")
    print(f"Exception args: {error.args}")
 
# Jetzt mit einem Dictionary versuchen
grades = {"Alice": 95}
 
try:
    grade = grades["Bob"]
except KeyError as error:
    print(f"KeyError message: {error}")
    print(f"Missing key: {error.args[0]}")

Output:

IndexError message: list index out of range
Exception args: ('list index out of range',)
KeyError message: 'Bob'
Missing key: Bob

Beachten Sie, wie KeyError in seiner Meldung den tatsächlich fehlenden Schlüssel enthält. Unterschiedliche Ausnahmetypen liefern unterschiedliche nützliche Informationen, auf die Sie über das Ausnahmeobjekt zugreifen können.

25.3) else und finally mit try-Blöcken verwenden

25.3.1) Die else-Klausel: Code, der nur bei Erfolg ausgeführt wird

Die else-Klausel in einem try-except-Block läuft nur, wenn keine Ausnahme aufgetreten ist im try-Block. Das ist nützlich für Code, der nur ausgeführt werden soll, wenn die riskante Operation erfolgreich ist:

python
# else für Code verwenden, der nur bei Erfolg läuft
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
except ValueError:
    print("That's not a valid number!")
else:
    # Das läuft nur, wenn int(user_input) erfolgreich war
    print(f"Successfully converted: {number}")
    squared = number ** 2
    print(f"The square of {number} is {squared}")

Wenn der Benutzer „5“ eingibt:

Enter a number:
5
Successfully converted: 5
The square of 5 is 25

Wenn der Benutzer „hello“ eingibt:

Enter a number:
hello
That's not a valid number!

Warum else verwenden, statt den Code einfach ans Ende des try-Blocks zu setzen? Es gibt zwei wichtige Gründe:

  1. Klarheit: Die else-Klausel macht explizit, dass dieser Code nur bei Erfolg ausgeführt wird
  2. Ausnahme-Scope: Ausnahmen, die in der else-Klausel ausgelöst werden, werden nicht von den vorhergehenden except-Klauseln abgefangen

Hier ist ein Beispiel, das zeigt, warum der zweite Punkt wichtig ist:

python
# Demonstration, warum else für den Ausnahme-Scope nützlich ist
try:
    number_1 = int(input("Enter a number_1: "))
except ValueError:
    print("Invalid input!")
else:
    # Wenn hier ein Fehler auftritt, wird er nicht vom except oben abgefangen
    # Das hilft, zwischen Eingabefehlern und Verarbeitungsfehlern zu unterscheiden
    number_2 = int(input("Enter a number_2: ")) # Could raise ValueError

Wenn wir number_2 = int(input(...)) zusammen mit number_1 in den try-Block setzen würden, würde jeder ValueError von beiden Eingaben durch dieselbe except ValueError-Klausel abgefangen werden. Damit wäre es unmöglich zu erkennen, welche Eingabe das Problem verursacht hat.

Indem wir number_2 = int(input(...)) in den else-Block setzen, trennen wir die Fehlerbehandlung. Die except-Klausel fängt nur Fehler von number_1 ab, während Fehler von number_2 eine nicht abgefangene Ausnahme mit vollständigem Traceback auslösen – wodurch klar ist, dass die zweite Eingabe fehlgeschlagen ist, nicht die erste.

25.3.2) Die finally-Klausel: Code, der immer ausgeführt wird

Die finally-Klausel enthält Code, der in jedem Fall ausgeführt wird – egal, ob eine Ausnahme aufgetreten ist oder nicht, egal, ob sie abgefangen wurde oder nicht. Das ist entscheidend für Aufräumarbeiten, die immer passieren müssen:

python
# finally für Aufräumarbeiten verwenden
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
    result = 100 / number
    print(f"Result: {result}")
except ValueError:
    print("Invalid number!")
except ZeroDivisionError:
    print("Cannot divide by zero!")
finally:
    print("Calculation attempt completed.")

Wenn der Benutzer „5“ eingibt:

Enter a number:
5
Result: 20.0
Calculation attempt completed.

Wenn der Benutzer „hello“ eingibt:

Enter a number:
hello
Invalid number!
Calculation attempt completed.

Wenn der Benutzer „0“ eingibt:

Enter a number:
0
Cannot divide by zero!
Calculation attempt completed.

Der finally-Block läuft in allen drei Fällen! Das ist das zentrale Verhalten von finally: Es wird immer ausgeführt, unabhängig davon, was im try-Block passiert ist.

Nein

Ja

Ja

Nein

Ja

Nein

try-Block starten

Ausnahme ausgelöst?

try-Block abschließen

Ausnahme abgefangen?

else-Block ausführen, falls vorhanden

Passenden except-Block ausführen

Ausnahme propagiert

finally-Block ausführen

Wurde die Ausnahme abgefangen?

Nach try-except-finally fortfahren

Ausnahme propagiert weiter

25.3.3) try, except, else und finally kombinieren

Sie können alle vier Klauseln zusammen verwenden, um eine umfassende Ausnahmebehandlung zu erstellen:

python
# Vollständige Struktur zur Ausnahmebehandlung
print("Enter a number to calculate its reciprocal:")
user_input = input()
 
try:
    # Riskante Operationen
    number = int(user_input)
    reciprocal = 1 / number
except ValueError:
    # Umwandlungsfehler behandeln
    print("Error: Input must be a valid integer.")
except ZeroDivisionError:
    # Division durch Null behandeln
    print("Error: Cannot calculate reciprocal of zero.")
else:
    # Code nur bei Erfolg
    print(f"The reciprocal of {number} is {reciprocal}")
    print(f"Verification: {number} × {reciprocal} = {number * reciprocal}")
finally:
    # Aufräumcode, der immer läuft
    print("Reciprocal calculation completed.")

Wenn der Benutzer „4“ eingibt:

Enter a number to calculate its reciprocal:
4
The reciprocal of 4 is 0.25
Verification: 4 × 0.25 = 1.0
Reciprocal calculation completed.

Wenn der Benutzer „hello“ eingibt:

Enter a number to calculate its reciprocal:
hello
Error: Input must be a valid integer.
Reciprocal calculation completed.

Wenn der Benutzer „0“ eingibt:

Enter a number to calculate its reciprocal:
0
Error: Cannot calculate reciprocal of zero.
Reciprocal calculation completed.

Der Ausführungsablauf ist:

  1. Der try-Block wird immer zuerst ausgeführt
  2. Wenn eine Ausnahme auftritt, wird der passende except-Block ausgeführt
  3. Wenn keine Ausnahme auftritt, wird der else-Block ausgeführt (falls vorhanden)
  4. Der finally-Block wird immer zuletzt ausgeführt, unabhängig davon, was passiert ist

25.4) Ausnahmen gezielt mit raise auslösen

25.4.1) Warum Ausnahmen auslösen?

Bisher haben wir Ausnahmen abgefangen, die Python automatisch auslöst. Aber manchmal müssen Sie in Ihrem eigenen Code bewusst eine Ausnahme auslösen. Das ist nützlich, wenn:

  1. Sie eine ungültige Situation erkennen, die Ihr Code nicht behandeln kann
  2. Sie Regeln oder Einschränkungen erzwingen möchten
  3. Sie einen Fehler an Code signalisieren möchten, der Ihre Funktion aufgerufen hat

Eine Ausnahme auszulösen ist Pythons Art zu sagen: „Ich kann nicht weitermachen – etwas ist falsch, und wer mich aufgerufen hat, muss sich darum kümmern.“

Die Syntax ist einfach: raise ExceptionType("error message")

Hier ist ein grundlegendes Beispiel:

python
# Eine Ausnahme gezielt auslösen
age = -5
 
if age < 0:
    raise ValueError("Age cannot be negative!")
 
print(f"Age: {age}")  # Diese Zeile wird nie ausgeführt

Output:

Traceback (most recent call last):
  File "example.py", line 5, in <module>
    raise ValueError("Age cannot be negative!")
ValueError: Age cannot be negative!

Wenn Python auf raise trifft, erstellt es sofort eine Ausnahme und beginnt nach einem except-Block zu suchen, der sie behandeln kann. Gibt es keinen, wird das Programm mit einem Traceback beendet.

25.4.2) Ausnahmen in Funktionen auslösen

Ausnahmen auszulösen ist besonders in Funktionen nützlich, um Eingaben zu validieren und Einschränkungen durchzusetzen:

python
# Funktion, die Eingaben durch das Auslösen von Ausnahmen validiert
def calculate_discount(price, discount_percent):
    """Calculate discounted price.
    
    Args:
        price: Original price (must be positive)
        discount_percent: Discount percentage (must be 0-100)
    
    Returns:
        Discounted price
    
    Raises:
        ValueError: If inputs are invalid
    """
    if price < 0:
        raise ValueError("Price cannot be negative!")
    
    if discount_percent < 0 or discount_percent > 100:
        raise ValueError("Discount must be between 0 and 100!")
    
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount
 
# Die Funktion verwenden
try:
    final_price = calculate_discount(100, 20)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Output:

Final price: $80.0

Jetzt probieren wir es mit ungültigen Eingaben:

python
# Ungültiger Preis
try:
    final_price = calculate_discount(-50, 20)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Output:

Error: Price cannot be negative!
python
# Ungültiger Rabatt
try:
    final_price = calculate_discount(100, 150)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Output:

Error: Discount must be between 0 and 100!

Indem die Funktion Ausnahmen auslöst, kommuniziert sie klar, was schiefgelaufen ist. Der aufrufende Code kann dann entscheiden, wie er den Fehler behandelt – vielleicht indem er den Benutzer nach einer neuen Eingabe fragt, Standardwerte verwendet oder den Fehler protokolliert.

25.4.3) Den richtigen Ausnahmetyp wählen

Python hat viele eingebaute Ausnahmetypen, und den richtigen zu wählen macht Ihren Code verständlicher. Hier sind die am häufigsten verwendeten Ausnahmen für Validierung:

  • ValueError: Verwenden Sie diese, wenn ein Wert den richtigen Typ hat, aber einen unpassenden Wert (z. B. negatives Alter, ungültiger Prozentsatz)
  • TypeError: Verwenden Sie diese, wenn ein Wert ein komplett falscher Typ ist (z. B. String statt Zahl)
  • KeyError: Verwenden Sie diese, wenn ein Dictionary-Schlüssel nicht existiert
  • IndexError: Verwenden Sie diese, wenn ein Sequenzindex außerhalb des Bereichs liegt

Hier ist ein Beispiel, das verschiedene Ausnahmetypen zeigt:

python
# Geeignete Ausnahmetypen verwenden
def get_student_grade(grades, student_name):
    """Get a student's grade from the grades dictionary.
    
    Args:
        grades: Dictionary mapping student names to grades
        student_name: Name of the student
    
    Returns:
        The student's grade
    
    Raises:
        TypeError: If grades is not a dictionary
        KeyError: If student_name is not in grades
        ValueError: If the grade is invalid
    """
    if not isinstance(grades, dict):
        raise TypeError("Grades must be a dictionary!")
    
    if student_name not in grades:
        raise KeyError(f"Student '{student_name}' not found!")
    
    grade = grades[student_name]
    
    if not (0 <= grade <= 100):
        raise ValueError(f"Invalid grade: {grade} (must be 0-100)")
    
    return grade
 
# Test mit gültigen Daten
grades = {"Alice": 95, "Bob": 87, "Carol": 92}
 
try:
    grade = get_student_grade(grades, "Alice")
    print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Output:

Alice's grade: 95
python
# Test mit fehlendem Studenten
try:
    grade = get_student_grade(grades, "David")
    print(f"David's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Output:

Error: Student 'David' not found!
python
# Test mit falschem Typ
try:
    grade = get_student_grade("not a dict", "Alice")
    print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Output:

Error: Grades must be a dictionary!

Die Verwendung des passenden Ausnahmetyps hilft anderen Programmierern (und Ihrem zukünftigen Ich) zu verstehen, welche Art von Fehler aufgetreten ist.

25.4.4) Ausnahmen erneut auslösen

Manchmal möchten Sie eine Ausnahme abfangen, etwas tun (wie Logging) und dann die Ausnahme weiter propagieren lassen. Das können Sie tun, indem Sie raise ohne Argumente innerhalb eines except-Blocks verwenden:

python
# Eine Ausnahme nach dem Logging erneut auslösen
def divide_numbers(a, b):
    """Divide two numbers with error logging."""
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("ERROR LOG: Division by zero attempted")
        print(f"  Numerator: {a}, Denominator: {b}")
        raise  # Dieselbe Ausnahme erneut auslösen
 
# Die Funktion verwenden
try:
    result = divide_numbers(10, 0)
    print(f"Result: {result}")
except ZeroDivisionError:
    print("Cannot divide by zero!")

Output:

ERROR LOG: Division by zero attempted
  Numerator: 10, Denominator: 0
Cannot divide by zero!

Die raise-Anweisung ohne Argumente löst die Ausnahme, die gerade abgefangen wurde, erneut aus. Das ist nützlich, wenn Sie:

  1. Den Fehler protokollieren oder aufzeichnen wollen
  2. Aufräumarbeiten durchführen wollen
  3. Den Fehler an den Aufrufer weitergeben wollen

25.4.5) Ausnahmen aus Ausnahmen auslösen

Manchmal möchten Sie beim Behandeln einer Ausnahme eine neue Ausnahme auslösen und dabei den Kontext des ursprünglichen Fehlers bewahren. Python 3 bietet dafür die Syntax raise ... from ...:

python
# Eine neue Ausnahme aus einer bestehenden auslösen
def load_config(config_dict, key):
    """Load configuration value from dictionary."""
    try:
        config_value = config_dict[key]
        
        # Versuchen, als Integer zu parsen
        parsed_value = int(config_value)
        return parsed_value
        
    except KeyError as error:
        raise RuntimeError(f"Configuration key missing: {key}") from error
    except ValueError as error:
        raise RuntimeError(f"Invalid configuration format for {key}") from error
 
# Die Funktion verwenden
config = {"timeout": "30", "retries": "5"}
 
try:
    value = load_config(config, "timeout")
    print(f"Config value: {value}")
except RuntimeError as error:
    print(f"Configuration error: {error}")
    print(f"Original cause: {error.__cause__}")

Output:

Config value: 30

Wenn der Schlüssel nicht existiert:

python
try:
    value = load_config(config, "missing_key")
    print(f"Config value: {value}")
except RuntimeError as error:
    print(f"Configuration error: {error}")
    print(f"Original cause: {error.__cause__}")

Output:

Configuration error: Configuration key missing: missing_key
Original cause: 'missing_key'

Das Schlüsselwort from verknüpft die neue Ausnahme mit der ursprünglichen. Dadurch entsteht eine Kette von Ausnahmen, die beim Debugging hilft – Sie können sowohl sehen, was auf hoher Ebene schiefgelaufen ist (Konfigurationsfehler) als auch, was die zugrunde liegende Ursache war (Schlüssel nicht gefunden).


Ausnahmebehandlung ist eines der wichtigsten Werkzeuge, um zuverlässige Programme zu schreiben. Mit try-except-Blöcken können Sie Probleme vorwegnehmen, sie souverän behandeln und Ihren Benutzern eine bessere Erfahrung bieten. Denken Sie daran:

  • Verwenden Sie try-except, um erwartete Fehler souverän zu behandeln
  • Fangen Sie spezifische Ausnahmetypen ab, statt ein nacktes except zu verwenden
  • Verwenden Sie else für Code, der nur bei Erfolg laufen soll
  • Verwenden Sie finally für Aufräumcode, der immer laufen muss
  • Lösen Sie in Ihrem eigenen Code Ausnahmen aus, um Probleme zu signalisieren
  • Wählen Sie passende Ausnahmetypen, um Fehler klar zu machen
  • Geben Sie hilfreiche Fehlermeldungen aus, die erklären, was schiefgelaufen ist

Im nächsten Kapitel lernen wir Techniken der defensiven Programmierung kennen, die Ausnahmebehandlung mit Eingabevalidierung und anderen Strategien kombinieren, um unsere Programme noch robuster zu machen.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai