try/except in Python verstehen
Lerne, wie du Fehler in Python mit try/except abfängst und deine Programme robuster machst.
try/except in Python verstehen
Stell dir vor, du baust eine App und ein Benutzer gibt statt einer Zahl plötzlich “Hallo” ein. Ohne Fehlerbehandlung stürzt dein Programm einfach ab. Das wollen wir verhindern! In diesem Tutorial lernst du, wie du mit try/except Fehler elegant abfängst.
Warum Fehlerbehandlung?
Programme sollen nicht abstürzen — zumindest nicht ohne Grund. In der echten Welt passieren ständig unerwartete Dinge:
- Ein Benutzer gibt ungültige Daten ein
- Eine Datei existiert nicht
- Die Internetverbindung bricht ab
- Eine Division durch Null wird versucht
Ohne Fehlerbehandlung beendet Python dein Programm sofort mit einer Fehlermeldung. Mit Fehlerbehandlung kannst du stattdessen kontrolliert reagieren.
# Ohne Fehlerbehandlung - Programm stürzt ab!
alter = int(input("Wie alt bist du? ")) # Benutzer gibt "abc" ein -> CRASH!
# Mit Fehlerbehandlung - Programm läuft weiter
try:
alter = int(input("Wie alt bist du? "))
except ValueError:
print("Bitte gib eine gültige Zahl ein!")
Arten von Fehlern
Python unterscheidet grundsätzlich zwei Fehlerarten:
SyntaxError — Fehler im Code selbst
Diese Fehler entstehen, wenn du die Python-Syntax verletzt. Sie werden vor der Ausführung erkannt:
# SyntaxError: fehlende Klammer
print("Hallo"
# SyntaxError: falsche Einrückung
def gruss():
print("Hallo")
# SyntaxError: ungültiges Schlüsselwort
class = "Python"
SyntaxErrors kannst du nicht mit try/except abfangen — du musst den Code korrigieren!
Exceptions — Fehler zur Laufzeit
Diese Fehler treten erst während der Ausführung auf. Sie können mit try/except abgefangen werden:
# Erst zur Laufzeit tritt der Fehler auf
zahl = int("abc") # ValueError
ergebnis = 10 / 0 # ZeroDivisionError
liste = [1, 2, 3]
print(liste[10]) # IndexError
Häufige Exceptions
Hier sind die Exceptions, die dir am häufigsten begegnen werden:
ValueError
Tritt auf, wenn eine Funktion einen Wert mit dem richtigen Typ, aber falschem Inhalt bekommt:
int("abc") # ValueError: invalid literal for int()
int("12.5") # ValueError: invalid literal for int()
float("hallo") # ValueError: could not convert string to float
TypeError
Tritt auf, wenn eine Operation auf den falschen Datentyp angewendet wird:
"Hallo" + 5 # TypeError: can only concatenate str to str
len(42) # TypeError: object of type 'int' has no len()
"Alter: " - 5 # TypeError: unsupported operand type(s)
KeyError
Tritt auf, wenn ein Schlüssel in einem Dictionary nicht existiert:
person = {"name": "Anna", "alter": 25}
print(person["beruf"]) # KeyError: 'beruf'
IndexError
Tritt auf, wenn ein Index außerhalb des gültigen Bereichs liegt:
farben = ["rot", "grün", "blau"]
print(farben[5]) # IndexError: list index out of range
FileNotFoundError
Tritt auf, wenn eine Datei nicht gefunden wird:
with open("geheim.txt") as f: # FileNotFoundError
inhalt = f.read()
ZeroDivisionError
Tritt auf, wenn durch Null geteilt wird:
ergebnis = 100 / 0 # ZeroDivisionError: division by zero
try/except Grundsyntax
Die grundlegende Struktur sieht so aus:
try:
# Code, der einen Fehler verursachen könnte
riskanter_code()
except FehlerTyp:
# Code, der bei einem Fehler ausgeführt wird
print("Ein Fehler ist aufgetreten!")
Ein konkretes Beispiel:
try:
zahl = int(input("Gib eine Zahl ein: "))
ergebnis = 100 / zahl
print(f"100 / {zahl} = {ergebnis}")
except ValueError:
print("Das war keine gültige Zahl!")
except ZeroDivisionError:
print("Division durch Null ist nicht erlaubt!")
Mehrere except-Blöcke
Du kannst verschiedene Fehlertypen unterschiedlich behandeln:
def daten_verarbeiten(daten, index, schluessel):
try:
element = daten[index]
wert = element[schluessel]
ergebnis = 100 / wert
return ergebnis
except IndexError:
print(f"Index {index} existiert nicht!")
except KeyError:
print(f"Schlüssel '{schluessel}' nicht gefunden!")
except ZeroDivisionError:
print("Der Wert darf nicht Null sein!")
except TypeError:
print("Ungültiger Datentyp!")
# Testen
daten = [{"wert": 5}, {"wert": 0}, {"wert": 10}]
daten_verarbeiten(daten, 0, "wert") # Gibt 20.0 zurück
daten_verarbeiten(daten, 5, "wert") # Index 5 existiert nicht!
daten_verarbeiten(daten, 0, "preis") # Schlüssel 'preis' nicht gefunden!
daten_verarbeiten(daten, 1, "wert") # Der Wert darf nicht Null sein!
Du kannst auch mehrere Exceptions in einem Block zusammenfassen:
try:
zahl = int(input("Zahl: "))
ergebnis = 100 / zahl
except (ValueError, ZeroDivisionError):
print("Ungültige Eingabe oder Division durch Null!")
except mit Alias (as e)
Mit as bekommst du Zugriff auf die genaue Fehlermeldung:
try:
zahl = int("abc")
except ValueError as e:
print(f"Fehler aufgetreten: {e}")
# Ausgabe: Fehler aufgetreten: invalid literal for int() with base 10: 'abc'
Das ist besonders nützlich zum Loggen von Fehlern:
import logging
def datei_lesen(pfad):
try:
with open(pfad, "r") as f:
return f.read()
except FileNotFoundError as e:
logging.error(f"Datei nicht gefunden: {e}")
return None
except PermissionError as e:
logging.error(f"Keine Berechtigung: {e}")
return None
try/except/else
Der else-Block wird nur ausgeführt, wenn kein Fehler aufgetreten ist:
try:
zahl = int(input("Gib eine Zahl ein: "))
except ValueError:
print("Das war keine Zahl!")
else:
# Wird nur ausgeführt, wenn KEIN Fehler aufgetreten ist
print(f"Super! Du hast {zahl} eingegeben.")
print(f"Das Doppelte ist {zahl * 2}.")
Warum else statt den Code einfach in try zu packen? Der else-Block sorgt dafür, dass nur der riskante Code im try-Block steht. Das macht den Code übersichtlicher und verhindert, dass du versehentlich Fehler aus dem “sicheren” Code abfängst:
try:
# Nur der riskante Teil
wert = int(eingabe)
except ValueError:
print("Keine gültige Zahl!")
else:
# Sicherer Code, der von wert abhängt
ergebnis = berechne_kompliziert(wert)
speichere_ergebnis(ergebnis)
try/except/finally
Der finally-Block wird immer ausgeführt — egal ob ein Fehler aufgetreten ist oder nicht:
try:
datei = open("daten.txt", "r")
inhalt = datei.read()
print(inhalt)
except FileNotFoundError:
print("Datei nicht gefunden!")
finally:
# Wird IMMER ausgeführt
print("Aufräumarbeiten...")
try:
datei.close()
except NameError:
pass # Datei wurde nie geöffnet
finally ist besonders wichtig für Aufräumarbeiten wie das Schließen von Dateien oder Datenbankverbindungen:
verbindung = None
try:
verbindung = datenbank_verbinden()
daten = verbindung.abfrage("SELECT * FROM benutzer")
verarbeite(daten)
except DatenbankFehler as e:
print(f"Datenbankfehler: {e}")
finally:
if verbindung:
verbindung.schliessen()
print("Verbindung geschlossen.")
Die komplette Struktur
Du kannst try, except, else und finally auch kombinieren:
try:
# Riskanter Code
ergebnis = riskante_operation()
except TypA as e:
# Bei Fehler Typ A
behandle_typ_a(e)
except TypB as e:
# Bei Fehler Typ B
behandle_typ_b(e)
else:
# Kein Fehler aufgetreten
verwende_ergebnis(ergebnis)
finally:
# Wird immer ausgeführt
aufraeumen()
Bare except vermeiden
Ein häufiger Anfängerfehler ist das blanke except ohne Angabe eines Fehlertyps:
# SCHLECHT - fängt ALLE Fehler ab, auch unerwartete!
try:
riskanter_code()
except:
print("Irgendein Fehler...")
Das Problem: except: fängt wirklich alles ab — auch KeyboardInterrupt (Strg+C) und SystemExit. Dein Programm lässt sich dann nicht mehr mit Strg+C abbrechen!
# BESSER - fängt nur "normale" Exceptions ab
try:
riskanter_code()
except Exception as e:
print(f"Fehler: {e}")
Der Unterschied:
# except: -> fängt BaseException (ALLES)
# except Exception -> fängt Exception und Unterklassen (fast alles)
# NICHT: KeyboardInterrupt, SystemExit, GeneratorExit
Faustregel: Fange immer so spezifisch wie möglich ab:
# AM BESTEN - spezifischer Fehlertyp
try:
zahl = int(eingabe)
except ValueError:
print("Keine gültige Zahl!")
# OK - wenn du mehrere Fehler erwartest
try:
komplexe_operation()
except Exception as e:
logging.error(f"Unerwarteter Fehler: {e}")
# NIEMALS - bare except
try:
irgendwas()
except:
pass # Fehler werden verschluckt!
Praxis: Sichere Benutzereingabe
Eine der häufigsten Anwendungen ist die Validierung von Benutzereingaben:
def sichere_ganzzahl(nachricht, minimum=None, maximum=None):
"""Fragt den Benutzer nach einer Ganzzahl mit Validierung."""
while True:
try:
zahl = int(input(nachricht))
except ValueError:
print("Bitte gib eine gültige Ganzzahl ein!")
continue
if minimum is not None and zahl < minimum:
print(f"Die Zahl muss mindestens {minimum} sein!")
elif maximum is not None and zahl > maximum:
print(f"Die Zahl darf höchstens {maximum} sein!")
else:
return zahl
# Verwendung
alter = sichere_ganzzahl("Dein Alter: ", minimum=0, maximum=150)
print(f"Du bist {alter} Jahre alt.")
Praxis: Sicheres Datei-Handling
def datei_sicher_lesen(dateipfad):
"""Liest eine Datei und gibt den Inhalt zurück oder None bei Fehler."""
try:
with open(dateipfad, "r", encoding="utf-8") as datei:
inhalt = datei.read()
except FileNotFoundError:
print(f"Die Datei '{dateipfad}' wurde nicht gefunden.")
return None
except PermissionError:
print(f"Keine Berechtigung zum Lesen von '{dateipfad}'.")
return None
except UnicodeDecodeError:
print(f"Die Datei '{dateipfad}' hat eine ungültige Kodierung.")
return None
else:
print(f"Datei erfolgreich gelesen ({len(inhalt)} Zeichen).")
return inhalt
def json_laden(dateipfad):
"""Lädt JSON-Daten sicher aus einer Datei."""
import json
inhalt = datei_sicher_lesen(dateipfad)
if inhalt is None:
return None
try:
daten = json.loads(inhalt)
except json.JSONDecodeError as e:
print(f"Ungültiges JSON-Format: {e}")
return None
else:
return daten
# Verwendung
konfiguration = json_laden("config.json")
if konfiguration:
print(f"Konfiguration geladen: {konfiguration}")
else:
print("Standard-Konfiguration wird verwendet.")
Praxis: Wiederholte Versuche
Manchmal möchtest du eine Operation mehrfach versuchen:
import time
def mit_wiederholung(funktion, max_versuche=3, wartezeit=1):
"""Führt eine Funktion aus und wiederholt bei Fehler."""
for versuch in range(1, max_versuche + 1):
try:
ergebnis = funktion()
return ergebnis
except Exception as e:
print(f"Versuch {versuch}/{max_versuche} fehlgeschlagen: {e}")
if versuch < max_versuche:
print(f"Warte {wartezeit} Sekunde(n)...")
time.sleep(wartezeit)
else:
print("Alle Versuche fehlgeschlagen!")
raise # Letzten Fehler weiterleiten
Übungen
Übung 1: Taschenrechner mit Fehlerbehandlung
Schreibe einen einfachen Taschenrechner, der zwei Zahlen und einen Operator vom Benutzer abfragt. Fange alle möglichen Fehler ab.
def taschenrechner():
"""
Aufgabe:
- Frage nach zwei Zahlen und einem Operator (+, -, *, /)
- Fange ValueError ab (ungültige Zahlen)
- Fange ZeroDivisionError ab (Division durch Null)
- Gib bei ungültigem Operator eine Meldung aus
- Verwende eine while-Schleife für wiederholte Berechnung
"""
pass # Dein Code hier!
Übung 2: Sichere Liste
Schreibe eine Funktion, die sicher auf Listenelemente zugreift:
def sicherer_zugriff(liste, index, standard=None):
"""
Aufgabe:
- Greife sicher auf liste[index] zu
- Gib bei IndexError den Standardwert zurück
- Gib bei TypeError (z.B. index ist kein int) eine Meldung aus
"""
pass # Dein Code hier!
# Tests
zahlen = [10, 20, 30]
print(sicherer_zugriff(zahlen, 1)) # 20
print(sicherer_zugriff(zahlen, 10)) # None
print(sicherer_zugriff(zahlen, 10, -1)) # -1
Übung 3: Konfigurationsdatei laden
Erstelle eine Funktion, die eine INI-ähnliche Konfigurationsdatei liest:
def konfig_laden(dateipfad):
"""
Aufgabe:
- Lies die Datei Zeile für Zeile
- Jede Zeile hat das Format: schluessel=wert
- Überspringe leere Zeilen und Kommentare (beginnen mit #)
- Fange FileNotFoundError und ValueError ab
- Gib ein Dictionary zurück
"""
pass # Dein Code hier!
# Beispiel-Nutzung:
# konfig = konfig_laden("einstellungen.ini")
# print(konfig) # {"name": "MeineApp", "version": "1.0", "debug": "true"}
Pro-Tipp: Verwende in der Praxis den with-Befehl (Context Manager) für Dateien und Ressourcen, anstatt manuell finally mit close() zu nutzen. Der with-Block schließt Ressourcen automatisch — auch wenn ein Fehler auftritt:
# Statt:
try:
f = open("datei.txt")
inhalt = f.read()
finally:
f.close()
# Besser:
with open("datei.txt") as f:
inhalt = f.read()
# Datei wird automatisch geschlossen!