Aufwärmen · Aufgabe 1 von 7
// 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.
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
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)Ü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"):Ü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())Üben · Aufgabe 5 von 7
Eine Funktion hat eine try-Anweisung mit except ValueError und finally. Ordnen Sie jedem Fall zu, was passiert.
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())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.pyAusgabe
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
Hinweis 1
Setzen Sie start, die Prüfung und die done-Zeile in einen try-Block.
Hinweis 2
Die end-Zeile gehört in finally, damit sie läuft, ob der try-Block auslöste oder nicht.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
Eine with-Anweisung kann zwei Dateien öffnen, durch ein Komma getrennt: with open(…) as fin, open(…) as fout:.
Hinweis 2
Die Dateien öffnen von links nach rechts. Setzen Sie src nach vorn: Fehlt sie, findet das zweite open nie statt.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHä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 definedWarum, 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 syntaxWarum, 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.