Aufwärmen · Aufgabe 1 von 7
// B5.5 · ca. 35 Min. · Einstieg
Ausnahmen behandeln
Nach dieser Lektion läuft Ihr Programm weiter, wenn Eingaben oder Dateien fehlerhaft sind: Sie fangen die richtige Ausnahme ab, lösen eigene mit klarer Meldung aus und lesen im Traceback, was wo schiefging.
Lektion 5 von 5 in B5 Module, Dateien und Fehler
Danach können Sie
- Fehler mit try, except und else abfangen, mit einer except-Klausel pro Fehlertyp
- Mit raise eine Ausnahme mit klarer Meldung auslösen, wenn ein Wert ungültig ist
- Einen Traceback lesen: Ausnahmetyp, Meldung und fehlgeschlagene Zeile finden
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es voraus, bevor Sie weiterlesen: Was gibt das aus?
result = "start" try: result = int("12a") result = "converted" except ValueError: result = "not a number" print(result)Üben · Aufgabe 3 von 7
Ergänzen Sie das Schlüsselwort, das die Funktion mit diesem ValueError beendet.
def set_age(age): if age < 0: ____ ValueError("age must not be negative") return ageValueError(Üben · Aufgabe 4 von 7
Ein try, zwei except-Klauseln. Was gibt das aus?
def lookup(data, key): try: return 100 / data[key] except KeyError: return "missing" except ZeroDivisionError: return "zero" prices = {"tea": 0, "cake": 4} print(lookup(prices, "tea"), lookup(prices, "pie"), lookup(prices, "cake"))Üben · Aufgabe 5 von 7
Ordnen Sie jedem Ausdruck die Ausnahme zu, die er auslöst.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Die Ausnahme passiert in parse. Was gibt das aus?
def parse(text): return int(text) log = [] for text in ["7", "x", "3"]: try: value = parse(text) except ValueError: log.append("bad") else: log.append(value * 2) print(log)Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie ein Programm, das mit input() nach einem Alter fragt, bis die Antwort eine ganze Zahl ist. Bei einer falschen Antwort geben Sie sie mit !r und den Worten is not a whole number aus und fragen erneut. Danach geben Sie age: und die Zahl aus. Probieren Sie ten, eine leere Zeile, dann 12.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Bestellungen verarbeiten, ohne abzustürzen
Fünf Bestellungen laufen durch einen kleinen Laden. parse_quantity löst selbst ValueError für eine nicht positive Menge aus, int löst ihn für Text aus, der keine Zahl ist, und ein fehlender Artikel löst KeyError aus. Eine try-Anweisung behandelt alle drei, und else verkauft nur, wenn nichts schiefging.
main.py
def parse_quantity(text: str) -> int:
quantity = int(text)
if quantity <= 0:
raise ValueError(f"quantity must be positive, got {quantity}")
return quantity
stock = {"apples": 5, "pears": 2}
orders = [("apples", "2"), ("plums", "1"), ("pears", "two"), ("pears", "-1"), ("pears", "3")]
for item, text in orders:
try:
quantity = parse_quantity(text)
available = stock[item]
except KeyError:
print(f"{item}: not sold here")
except ValueError as err:
print(f"{item}: bad quantity ({err})")
else:
if quantity > available:
print(f"{item}: only {available} left")
else:
stock[item] = available - quantity
print(f"{item}: sold {quantity}, {stock[item]} left")
Ausführen mit
python main.pyAusgabe
apples: sold 2, 3 left
plums: not sold here
pears: bad quantity (invalid literal for int() with base 10: 'two')
pears: bad quantity (quantity must be positive, got -1)
pears: only 2 left- Ein except ValueError fängt sowohl den Fehler aus int als auch den, den parse_quantity selbst auslöst.
- err enthält die Ausnahme; in einem f-String erscheint ihre Meldung.
- Die letzte Bestellung ist keine Ausnahme: Zu wenige Birnen sind gewöhnliche Logik in else.
- Die Schleife endet nie vorzeitig: Nach jedem behandelten Fehler geht es mit der nächsten Bestellung weiter.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 3
Projekt 1: die To-do-Liste sicher laden
Modulprojekt, Schritt 1 von 3: eine To-do-Verwaltung, die ihre Liste in einer JSON-Datei hält. Schreiben Sie load_todos(path). Die Funktion gibt die gespeicherte Liste zurück. Gibt es die Datei noch nicht, geben Sie eine leere Liste zurück. Ist die Datei kein gültiges JSON, lösen Sie ValueError mit der Meldung "<path> is not valid JSON" aus, zum Beispiel todos.json is not valid JSON.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Setzen Sie den Block with open(...) in ein try. Eine fehlende Datei zu öffnen löst FileNotFoundError aus.
Hinweis 2
json.load löst für Text, der kein JSON ist, json.JSONDecodeError aus. Geben Sie ihm eine eigene except-Klausel.
Hinweis 3
In dieser Klausel: raise ValueError(f"{path} is not valid JSON").
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import json
def load_todos(path: str) -> list[dict]:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
raise ValueError(f"{path} is not valid JSON")
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import json
def load_todos(path: str) -> list[dict]:
with open(path, encoding="utf-8") as f:
return json.load(f)
test_main.py
from main import load_todos
def test_missing_file():
"""Eine fehlende Datei ergibt eine leere Liste"""
try:
got = load_todos("no_such_file.json")
except FileNotFoundError:
assert False, "load_todos löste FileNotFoundError aus; fangen Sie ihn ab und geben Sie [] zurück"
assert got == [], f"load_todos gab {got!r} zurück, erwartet war []"
def test_valid_file():
"""Eine gültige Datei ergibt ihre Liste"""
with open("t1.json", "w", encoding="utf-8") as f:
f.write('[{"title": "Buy milk", "done": false}]')
got = load_todos("t1.json")
assert got == [{"title": "Buy milk", "done": False}], f"load_todos gab {got!r} zurück"
def test_broken_file():
"""Eine kaputte Datei löst ValueError mit dem Dateinamen aus"""
with open("t2.json", "w", encoding="utf-8") as f:
f.write("[{not json")
try:
load_todos("t2.json")
except ValueError as err:
assert str(err) == "t2.json is not valid JSON", f"die Meldung war {str(err)!r}, erwartet war 't2.json is not valid JSON'"
else:
assert False, "load_todos kehrte bei einer kaputten Datei normal zurück; lösen Sie ValueError aus"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 3
Projekt 2: ein To-do hinzufügen
Schritt 2 von 3. load_todos ist fertig. Schreiben Sie add_todo(path, title): Entfernen Sie die Leerzeichen um title; bleibt nichts übrig, lösen Sie ValueError("title must not be empty") aus, ohne die Datei anzufassen. Sonst laden Sie die Liste, hängen {"title": title, "done": False} an, speichern sie mit json.dump und indent=2 zurück und geben sie zurück.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Prüfen Sie zuerst den Titel, bevor Sie etwas laden: title = title.strip(), dann if not title: raise ValueError("title must not be empty").
Hinweis 2
Gespeichert wird mit json.dump(todos, f, indent=2) innerhalb von with open(path, "w", encoding="utf-8") as f:.
Hinweis 3
Geben Sie die Liste nach dem Speichern zurück, damit der Aufrufer das neue To-do ausgeben kann.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import json
def load_todos(path: str) -> list[dict]:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
raise ValueError(f"{path} is not valid JSON")
def add_todo(path: str, title: str) -> list[dict]:
title = title.strip()
if not title:
raise ValueError("title must not be empty")
todos = load_todos(path)
todos.append({"title": title, "done": False})
with open(path, "w", encoding="utf-8") as f:
json.dump(todos, f, indent=2)
return todos
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import json
def load_todos(path: str) -> list[dict]:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
raise ValueError(f"{path} is not valid JSON")
def add_todo(path: str, title: str) -> list[dict]:
todos = load_todos(path)
todos.append({"title": title, "done": False})
return todos
test_main.py
import json
import os
from main import add_todo
def fresh(path):
if os.path.exists(path):
os.remove(path)
def test_saved():
"""Zwei To-dos landen in der Datei, in Reihenfolge"""
fresh("t1.json")
add_todo("t1.json", "Buy milk")
add_todo("t1.json", "Call Ada")
with open("t1.json", encoding="utf-8") as f:
got = json.load(f)
want = [{"title": "Buy milk", "done": False}, {"title": "Call Ada", "done": False}]
assert got == want, f"die Datei enthält {got!r}"
def test_stripped():
"""Der Titel wird ohne umgebende Leerzeichen gespeichert"""
fresh("t2.json")
got = add_todo("t2.json", " Water plants ")
assert got == [{"title": "Water plants", "done": False}], f"add_todo gab {got!r} zurück"
def test_empty_title():
"""Ein leerer Titel löst ValueError aus und lässt die Datei unverändert"""
fresh("t3.json")
add_todo("t3.json", "Buy milk")
try:
add_todo("t3.json", " ")
except ValueError as err:
assert str(err) == "title must not be empty", f"die Meldung war {str(err)!r}"
else:
assert False, "add_todo nahm einen leeren Titel an; lösen Sie ValueError aus"
with open("t3.json", encoding="utf-8") as f:
got = json.load(f)
assert len(got) == 1, f"nach dem leeren Titel enthält die Datei {got!r}"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 3 von 3
Projekt 3: die Befehlsschleife
Schritt 3 von 3. Das Programm liest Befehle, bis die Eingabe endet: add <title>, done <number> und list. Zwei Dinge fehlen. Lösen Sie in complete_todo ValueError(f"no to-do number {number}") aus, wenn die Nummer nicht zwischen 1 und der Länge der Liste liegt. Beenden Sie die Schleife, wenn input() EOFError auslöst, und fangen Sie ValueError aus run(line) ab, indem Sie error: und die Meldung ausgeben.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
In complete_todo vor todos[number - 1]: if not 1 <= number <= len(todos): raise ValueError(f"no to-do number {number}"). Ohne diese Prüfung würde done 0 das letzte To-do abhaken.
Hinweis 2
input() löst EOFError aus, wenn keine Eingabe mehr kommt. Umschließen Sie es: try: line = input() except EOFError: break.
Hinweis 3
Dann ein zweites try um run(line), mit except ValueError as err: print("error:", err). int("two") löst ebenfalls ValueError aus und ist damit abgedeckt.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import json
PATH = "todos.json"
def load_todos(path: str) -> list[dict]:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
raise ValueError(f"{path} is not valid JSON")
def save_todos(path: str, todos: list[dict]) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(todos, f, indent=2)
def add_todo(path: str, title: str) -> list[dict]:
title = title.strip()
if not title:
raise ValueError("title must not be empty")
todos = load_todos(path)
todos.append({"title": title, "done": False})
save_todos(path, todos)
return todos
def complete_todo(path: str, number: int) -> dict:
todos = load_todos(path)
if not 1 <= number <= len(todos):
raise ValueError(f"no to-do number {number}")
todo = todos[number - 1]
todo["done"] = True
save_todos(path, todos)
return todo
def show(path: str) -> None:
todos = load_todos(path)
if not todos:
print("nothing to do")
for number, todo in enumerate(todos, start=1):
mark = "x" if todo["done"] else " "
print(f"{number}. [{mark}] {todo['title']}")
def run(line: str) -> None:
command, _, argument = line.partition(" ")
if command == "add":
todos = add_todo(PATH, argument)
print("added:", todos[-1]["title"])
elif command == "done":
todo = complete_todo(PATH, int(argument))
print("done:", todo["title"])
elif command == "list":
show(PATH)
else:
raise ValueError(f"unknown command: {command}")
if __name__ == "__main__":
while True:
try:
line = input()
except EOFError:
break
try:
run(line)
except ValueError as err:
print("error:", err)
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import json
PATH = "todos.json"
def load_todos(path: str) -> list[dict]:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
raise ValueError(f"{path} is not valid JSON")
def save_todos(path: str, todos: list[dict]) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(todos, f, indent=2)
def add_todo(path: str, title: str) -> list[dict]:
title = title.strip()
if not title:
raise ValueError("title must not be empty")
todos = load_todos(path)
todos.append({"title": title, "done": False})
save_todos(path, todos)
return todos
def complete_todo(path: str, number: int) -> dict:
todos = load_todos(path)
# Raise ValueError here when number is not between 1 and len(todos).
todo = todos[number - 1]
todo["done"] = True
save_todos(path, todos)
return todo
def show(path: str) -> None:
todos = load_todos(path)
if not todos:
print("nothing to do")
for number, todo in enumerate(todos, start=1):
mark = "x" if todo["done"] else " "
print(f"{number}. [{mark}] {todo['title']}")
def run(line: str) -> None:
command, _, argument = line.partition(" ")
if command == "add":
todos = add_todo(PATH, argument)
print("added:", todos[-1]["title"])
elif command == "done":
todo = complete_todo(PATH, int(argument))
print("done:", todo["title"])
elif command == "list":
show(PATH)
else:
raise ValueError(f"unknown command: {command}")
if __name__ == "__main__":
# Stop at EOFError; print "error:" and the message for a ValueError.
while True:
line = input()
run(line)
test_main.py
import os
from learnrun import run_main
def fresh(*commands):
if os.path.exists("todos.json"):
os.remove("todos.json")
return run_main("\n".join(commands) + "\n").strip().splitlines()
def test_add_and_list():
"""add und list zeigen die To-dos, nummeriert"""
got = fresh("add Buy milk", "add Call Ada", "list")
want = ["added: Buy milk", "added: Call Ada", "1. [ ] Buy milk", "2. [ ] Call Ada"]
assert got == want, f"das Programm gab {got!r} aus"
def test_done():
"""done 2 hakt das zweite To-do ab"""
got = fresh("add Buy milk", "add Call Ada", "done 2", "list")
want = ["done: Call Ada", "1. [ ] Buy milk", "2. [x] Call Ada"]
assert got[-3:] == want, f"das Programm gab {got!r} aus"
def test_bad_numbers():
"""done mit falscher Nummer gibt einen Fehler aus, und das Programm läuft weiter"""
got = fresh("add Buy milk", "done 5", "done 0", "done two", "list")
assert got[1:3] == ["error: no to-do number 5", "error: no to-do number 0"], f"das Programm gab {got!r} aus"
assert got[3].startswith("error: "), f"für done two gab das Programm {got[3]!r} aus"
assert got[4:] == ["1. [ ] Buy milk"], f"das Programm gab {got!r} aus"
def test_other_errors():
"""Ein leerer Titel und ein unbekannter Befehl geben ebenfalls Fehler aus"""
got = fresh("add ", "fly away", "list")
want = ["error: title must not be empty", "error: unknown command: fly", "nothing to do"]
assert got == want, f"das Programm gab {got!r} aus"
def test_saved_between_runs():
"""Die Liste ist beim nächsten Lauf noch da"""
fresh("add Water plants")
got = run_main("list\n").strip().splitlines()
assert got == ["1. [ ] Water plants"], f"der zweite Lauf gab {got!r} aus"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHäufige Fehler
Den falschen Ausnahmetyp abfangen
try:
number = int("ten")
except TypeError:
print("not a number")
Was Python ausgibt
ValueError: invalid literal for int() with base 10: 'ten'Warum, und die Lösung
Eine except-Klausel fängt nur ihren eigenen Typ ab, und int("ten") löst ValueError aus, nicht TypeError; der Fehler geht also durch. Lesen Sie in der letzten Zeile des Tracebacks den echten Typ nach und schreiben Sie dann except ValueError:.
Einen String auslösen
age = -3
if age < 0:
raise "age must not be negative"
Was Python ausgibt
TypeError: exceptions must derive from BaseExceptionWarum, und die Lösung
raise braucht ein Ausnahmeobjekt, keinen bloßen String. Setzen Sie die Meldung in einen Ausnahmetyp: raise ValueError("age must not be negative").
Ein try ohne except
try:
value = int("5")
print(value)
Was Python ausgibt
SyntaxError: expected 'except' or 'finally' blockWarum, und die Lösung
Auf einen try-Block muss mindestens eine except-Klausel folgen (oder eine finally-Klausel, die später im Kurs kommt). Python weist die Datei ab, bevor irgendetwas davon läuft. Ergänzen Sie den Handler: except ValueError: und was dann geschehen soll.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.