Zum Inhalt springen
aviral gupta

// 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

Ende des Moduls

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
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus der letzten Lektion: Welche dieser Werte kann json.dumps speichern? Wählen Sie alle passenden.

    Wählen Sie alle zutreffenden aus.

  2. 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)
  3. Ü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 age
    ValueError(
  4. Ü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"))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Ausdruck die Ausnahme zu, die er auslöst.

  6. 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)
  7. 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.py

Ausgabe

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
  1. Hinweis 1

    Setzen Sie den Block with open(...) in ein try. Eine fehlende Datei zu öffnen löst FileNotFoundError aus.

  2. Hinweis 2

    json.load löst für Text, der kein JSON ist, json.JSONDecodeError aus. Geben Sie ihm eine eigene except-Klausel.

  3. 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.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.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
  1. 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").

  2. Hinweis 2

    Gespeichert wird mit json.dump(todos, f, indent=2) innerhalb von with open(path, "w", encoding="utf-8") as f:.

  3. 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.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.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
  1. 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.

  2. Hinweis 2

    input() löst EOFError aus, wenn keine Eingabe mehr kommt. Umschließen Sie es: try: line = input() except EOFError: break.

  3. 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.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Hä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 BaseException

Warum, 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' block

Warum, 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.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

try, except und else

Python führt den try-Block aus. Tritt dort eine Ausnahme auf, wird der Rest des Blocks übersprungen, und die erste except-Klausel mit passendem Typ läuft; höchstens ein Handler läuft. Passt keiner, wandert die Ausnahme weiter und beendet das Programm. except ValueError as err: gibt Ihnen die Ausnahme, str(err) ihre Meldung. Der else-Block läuft nur, wenn der try-Block nichts ausgelöst hat. Halten Sie den try-Block klein, nur mit der riskanten Zeile, und setzen Sie den Rest in else, damit Sie keine unerwarteten Fehler abfangen.

Eigene Ausnahmen mit raise

raise ValueError("age must not be negative") beendet die Funktion sofort und reicht die Ausnahme an den Aufrufer weiter, egal wie viele Aufrufe darüber. Ein try um den Aufruf fängt sie dort ab, genau wie einen eingebauten Fehler. Wählen Sie den passenden Typ: ValueError für einen Wert mit richtigem Typ, aber falschem Inhalt, TypeError für den falschen Typ, KeyError für einen fehlenden Schlüssel. Die Meldung ist für Menschen: Sagen Sie, was falsch war, und wo es hilft, welcher Wert.

Einen Traceback lesen

Eine nicht abgefangene Ausnahme gibt einen Traceback aus. Lesen Sie ihn von unten. Die letzte Zeile nennt Ausnahmetyp und Meldung, etwa ValueError: invalid literal for int() with base 10: 'ten'. Direkt darüber stehen Datei, Zeilennummer und Code der fehlgeschlagenen Zeile. Weiter oben stehen die Aufrufe, die dorthin führten: "most recent call last" heißt, der Aufruf nächst dem Fehler steht unten. Beginnen Sie mit der letzten Zeile und gehen Sie dann zu dieser Zeile in Ihrer Datei.

Quellen

Zuletzt geprüft am 29. September 2026