Zum Inhalt springen
aviral gupta

// I1.3 · ca. 30 Min. · Aufbau

Aufräumen mit finally und with

Nach dieser Lektion läuft Ihr Aufräumcode, wie auch immer ein Block endet, Sie sagen vorher, was return und break neben finally tun, und with schließt Ihre Dateien.

Lektion 3 von 5 in I1 Fehler im Detail

Danach können Sie

  • Vorhersagen, in welcher Reihenfolge try, except, else und finally laufen, mit und ohne Fehler
  • Vorhersagen, was return, break und eine unbehandelte Exception bei einer finally-Klausel tun
  • Dateien mit with schließen, auch wenn der Block scheitert, und zwei Dateien in einem with öffnen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus der letzten Lektion: In except KeyError schreiben Sie raise ConfigError("no port") from None. Welche Aussagen stimmen? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Was gibt das aus?

    log = []
    
    
    def divide(a, b):
        try:
            log.append("try")
            result = a / b
        except ZeroDivisionError:
            log.append("except")
        else:
            log.append("else")
        finally:
            log.append("finally")
    
    
    divide(1, 0)
    print(log)
  3. Üben · Aufgabe 3 von 7

    Setzen Sie das Schlüsselwort ein, damit "cleanup" angehängt wird, obwohl der try-Block auslöst, und der ValueError trotzdem beim Aufrufer ankommt.

    def use(log):
        try:
            log.append("work")
            raise ValueError("boom")
        ____:
            log.append("cleanup")
    :
  4. Üben · Aufgabe 4 von 7

    return in try. Was gibt das aus?

    def save():
        try:
            print("saving", end=" ")
            return "saved"
        finally:
            print("closing", end=" ")
    
    
    print(save())
  5. Üben · Aufgabe 5 von 7

    Eine Funktion hat eine try-Anweisung mit except ValueError und finally. Ordnen Sie jedem Fall zu, was passiert.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Python 3.14 gibt für diesen Code eine SyntaxWarning aus, aber er läuft. Was gibt er aus?

    def risky():
        try:
            return 1 / 0
        finally:
            return "finally wins"
    
    
    print(risky())
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie copy_numbered(src, dst). Es kopiert eine Textdatei, setzt vor jede Zeile ihre Nummer, einen Doppelpunkt und ein Leerzeichen und liefert die Zahl der Zeilen. Öffnen Sie beide Dateien in einer einzigen with-Anweisung, mit encoding="utf-8". Probieren Sie es mit einer Datei aus zwei Zeilen.

    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

Zeilen zählen, was auch schiefgeht

count_lines öffnet eine Datei mit with und löst bei einer leeren Datei ValueError aus. Seine finally-Klausel meldet das Ende, für eine gute Datei, eine leere und eine, die es nicht gibt. Jedes geöffnete Dateiobjekt landet in einer Liste, sodass die letzte Zeile prüfen kann, ob alle geschlossen wurden, auch das, bei dem ein Fehler auftrat.

main.py

from typing import TextIO

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("buy milk\ncall Ada\n")
with open("empty.txt", "w", encoding="utf-8") as f:
    pass

opened: list[TextIO] = []


def count_lines(path: str) -> int:
    try:
        with open(path, encoding="utf-8") as f:
            opened.append(f)
            lines = f.readlines()
            if not lines:
                raise ValueError(f"{path} is empty")
            return len(lines)
    finally:
        print(f"finished {path}")


for path in ["notes.txt", "empty.txt", "missing.txt"]:
    try:
        print(path, "has", count_lines(path), "lines")
    except ValueError as err:
        print("error:", err)
    except OSError as err:
        print("cannot open:", err.filename)

print("all closed:", all(f.closed for f in opened))

Ausführen mit

python main.py

Ausgabe

finished notes.txt
notes.txt has 2 lines
finished empty.txt
error: empty.txt is empty
finished missing.txt
cannot open: missing.txt
all closed: True
  • finished steht vor has 2 lines: count_lines läuft samt finally, bevor print sein Ergebnis zeigen kann.
  • finally läuft auch bei missing.txt, wo open scheitert, bevor der with-Block beginnt.
  • Auch die Datei, die ValueError auslöste, ist geschlossen: with schließt sie auf dem Weg nach draußen.
  • Geöffnet wurden nur zwei Dateien; die fehlende wurde nie zu einem Dateiobjekt.
Ä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 2

Aufräumen, das immer läuft

run_step(name, log, fail) hängt "start <name>" an, löst RuntimeError("<name> failed") aus, wenn fail wahr ist, und hängt sonst "done <name>" an. Sorgen Sie dafür, dass es immer "end <name>" als letzten Eintrag anhängt, auch wenn es auslöst, und dass der RuntimeError beim Aufrufer ankommt.

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 start, die Prüfung und die done-Zeile in einen try-Block.

  2. Hinweis 2

    Die end-Zeile gehört in finally, damit sie läuft, ob der try-Block auslöste oder nicht.

  3. Hinweis 3

    Fügen Sie keine except-Klausel hinzu: Der RuntimeError muss nach finally weiter zum Aufrufer.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def run_step(name: str, log: list[str], fail: bool) -> None:
    try:
        log.append(f"start {name}")
        if fail:
            raise RuntimeError(f"{name} failed")
        log.append(f"done {name}")
    finally:
        log.append(f"end {name}")
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

def run_step(name: str, log: list[str], fail: bool) -> None:
    log.append(f"start {name}")
    if fail:
        raise RuntimeError(f"{name} failed")
    log.append(f"done {name}")
    log.append(f"end {name}")

test_main.py

from main import run_step


def test_success():
    """Ein erfolgreicher Schritt protokolliert start, done und end"""
    log = []
    run_step("a", log, False)
    assert log == ["start a", "done a", "end a"], f"das Protokoll ist {log!r}"


def test_failure_logs_end():
    """Ein scheiternder Schritt protokolliert trotzdem end"""
    log = []
    try:
        run_step("b", log, True)
    except RuntimeError:
        pass
    assert log == ["start b", "end b"], f"das Protokoll ist {log!r}"


def test_failure_reaches_caller():
    """Der RuntimeError kommt beim Aufrufer an"""
    try:
        run_step("c", [], True)
    except RuntimeError as err:
        assert str(err) == "c failed", f"die Meldung war {str(err)!r}"
    else:
        assert False, "run_step kehrte normal zurück; der RuntimeError muss beim Aufrufer ankommen"

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 2

Zwei Dateien, ein with

copy_upper(src, dst) kopiert eine Textdatei in Großbuchstaben und liefert die Zahl der Zeilen. Es funktioniert, öffnet aber zuerst dst, sodass ein fehlendes src eine leere dst zurücklässt. Schreiben Sie es mit einer einzigen with-Anweisung neu, die src zuerst öffnet, beide mit encoding="utf-8", damit ein fehlendes src FileNotFoundError auslöst, bevor dst existiert.

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

    Eine with-Anweisung kann zwei Dateien öffnen, durch ein Komma getrennt: with open(…) as fin, open(…) as fout:.

  2. Hinweis 2

    Die Dateien öffnen von links nach rechts. Setzen Sie src nach vorn: Fehlt sie, findet das zweite open nie statt.

  3. Hinweis 3

    Im Block: über fin laufen, line.upper() nach fout schreiben und zählen. close() brauchen Sie nicht.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def copy_upper(src: str, dst: str) -> int:
    count = 0
    with open(src, encoding="utf-8") as fin, open(dst, "w", encoding="utf-8") as fout:
        for line in fin:
            fout.write(line.upper())
            count += 1
    return count
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

def copy_upper(src: str, dst: str) -> int:
    count = 0
    with open(dst, "w", encoding="utf-8") as fout:
        with open(src, encoding="utf-8") as fin:
            for line in fin:
                fout.write(line.upper())
                count += 1
    return count

test_main.py

import os

from main import copy_upper


def test_copy():
    """Die Kopie steht Zeile für Zeile in Großbuchstaben"""
    with open("in.txt", "w", encoding="utf-8") as f:
        f.write("hello\nworld\n")
    count = copy_upper("in.txt", "out.txt")
    with open("out.txt", encoding="utf-8") as f:
        got = f.read()
    assert got == "HELLO\nWORLD\n", f"out.txt enthält {got!r}"
    assert count == 2, f"copy_upper lieferte {count!r}, erwartet: 2"


def test_missing_source():
    """Eine fehlende Quelle löst FileNotFoundError aus und legt kein Ziel an"""
    if os.path.exists("never.txt"):
        os.remove("never.txt")
    try:
        copy_upper("no_such_file.txt", "never.txt")
    except FileNotFoundError:
        pass
    else:
        assert False, "copy_upper kehrte bei fehlender Quelle normal zurück"
    assert not os.path.exists("never.txt"), "never.txt wurde angelegt, obwohl die Quelle fehlt; öffnen Sie src zuerst"

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

Nach dem with-Block schreiben

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("first line\n")
f.write("second line\n")

Was Python ausgibt

ValueError: I/O operation on closed file.

Warum, und die Lösung

with schließt die Datei, wenn sein Block endet. Den Namen f gibt es danach noch, aber die Datei dahinter ist geschlossen. Rücken Sie jede Zeile, die die Datei nutzt, in den with-Block ein, oder öffnen Sie die Datei erneut.

In finally schließen, was nie geöffnet wurde

try:
    f = open("missing.txt", encoding="utf-8")
    print(f.read())
finally:
    f.close()

Was Python ausgibt

NameError: name 'f' is not defined

Warum, und die Lösung

open ist gescheitert, f wurde also nie zugewiesen, und finally scheitert dann zusätzlich zum FileNotFoundError. Öffnen Sie die Datei vor dem try (dann läuft finally erst, wenn sie offen ist), oder nutzen Sie einfach with open(...) as f:, das nur schließt, was es geöffnet hat.

else nach finally

try:
    x = 1
finally:
    print("done")
else:
    print("ok")

Was Python ausgibt

SyntaxError: invalid syntax

Warum, und die Lösung

Die Klauseln haben eine feste Reihenfolge: try, except, else, finally. else braucht außerdem mindestens eine except-Klausel davor. Setzen Sie finally ans Ende und ergänzen Sie die except-Klausel, von der else abhängt, oder lassen Sie else weg.

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

finally läuft immer, und zwar zuletzt

Eine finally-Klausel läuft als letzter Schritt einer try-Anweisung, ob eine Exception auftrat oder nicht. Die Reihenfolge: try, dann except (wenn ein Fehler behandelt wurde) oder else (wenn keiner auftrat), dann finally. Wird eine Exception nicht behandelt, läuft zuerst finally, dann geht die Exception weiter zum Aufrufer; das gilt auch für eine Exception aus except oder else. Deshalb gibt finally frei, was der try-Block benutzt hat: eine Datei, eine Sperre, eine Verbindung.

return, break und finally

Erreicht der try-Block return, break oder continue, läuft finally trotzdem, direkt bevor die Funktion zurückkehrt oder die Schleife weitergeht. Der Rückgabewert steht vorher fest; eine Variable in finally zu ändern, ändert ihn nicht. Ein return in finally selbst schlägt jedes andere return und verwirft sogar eine Exception, die gerade unterwegs war. Die Doku nennt das verwirrend, und seit 3.14 gibt Python für return, break oder continue in finally eine SyntaxWarning aus. finally nur zum Aufräumen nutzen.

with schließt die Datei für Sie

with open(path, encoding="utf-8") as f: öffnet die Datei und schließt sie, wenn der Block endet, normal oder durch eine Exception: wie ein try mit f.close() in finally, nur ohne die Gefahr, es zu vergessen. Nach dem Block gibt es f noch, aber f.closed ist True, und Lesen oder Schreiben löst ValueError aus. Ein with kann mehrere Dateien öffnen: with open(src) as fin, open(dst, "w") as fout:. Sie öffnen von links nach rechts; scheitert die erste, wird die zweite nie angelegt.

Quellen

Zuletzt geprüft am 29. September 2026