Aufwärmen · Aufgabe 1 von 7
// A1.4 · ca. 30 Min. · Vertiefung
Kontextmanager schreiben
Nach dieser Lektion schreiben Sie Klassen, die in einer with-Anweisung funktionieren, auf jedem Ausgangsweg aufräumen und genau die Ausnahmen unterdrücken, die Sie wählen.
Vorhersagen · Aufgabe 2 von 7
Sagen Sie voraus, bevor Sie weiterlesen: Was gibt das aus?
class Tag: def __init__(self, name): self.name = name def __enter__(self): print(f"<{self.name}>", end=" ") return self def __exit__(self, exc_type, exc, tb): print(f"</{self.name}>") with Tag("p") as t: print("hello from", t.name, end=" ")Üben · Aufgabe 3 von 7
Ergänzen Sie den Methodennamen, sodass with Session() as s: den Namen s an "cursor-1" bindet.
class Session: def ____(self): return "cursor-1" def __exit__(self, exc_type, exc, tb): passdef (self):Üben · Aufgabe 4 von 7
Der Block endet normal. Was gibt __exit__ aus?
class Probe: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): print(exc_type, exc, tb) with Probe(): passÜben · Aufgabe 5 von 7
Ordnen Sie jeder Situation zu, was passiert.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Quiet unterdrückt jede Ausnahme. Was gibt load() zurück?
class Quiet: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return True def load(): with Quiet(): value = int("x") return value return -1 print(load())Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie eine Klasse Indenter, deren Objekt sich in verschachtelten with-Anweisungen nutzen lässt. Jedes with erhöht beim Eintritt die Ebene um eins und senkt sie beim Austritt, und eine Methode print(text) gibt den Text nach so vielen Bindestrichen aus, wie die aktuelle Ebene angibt.
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
Eine Transaktion auf einem Dictionary
Transaction gibt dem Block über as eine Entwurfskopie. Endet der Block, kopiert __exit__ den Entwurf zurück: committed. Löst der Block eine Ausnahme aus, verwirft __exit__ den Entwurf und gibt None zurück; der Aufrufer sieht den Fehler, der Bestand bleibt unverändert. Lösen Sie im ersten Block eine Ausnahme aus, oder lassen Sie __exit__ True zurückgeben.
main.py
from types import TracebackType
class Transaction:
"""Changes a copy of the data; commits it only if the block succeeds."""
def __init__(self, data: dict[str, int]) -> None:
self.data = data
def __enter__(self) -> dict[str, int]:
self.draft = dict(self.data)
return self.draft
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
if exc_type is None:
self.data.clear()
self.data.update(self.draft)
print("committed")
else:
# Returning None (false) lets the exception continue.
print(f"rolled back after {exc_type.__name__}: {exc}")
stock = {"apples": 5, "pears": 2}
with Transaction(stock) as draft:
draft["apples"] -= 3
print(stock)
try:
with Transaction(stock) as draft:
draft["apples"] -= 1
draft["pears"] -= 5
if draft["pears"] < 0:
raise ValueError("not enough pears")
except ValueError:
print("the caller still sees the ValueError")
print(stock)
Ausführen mit
python main.pyAusgabe
committed
{'apples': 2, 'pears': 2}
rolled back after ValueError: not enough pears
the caller still sees the ValueError
{'apples': 2, 'pears': 2}- as draft bindet die Kopie, die __enter__ zurückgibt, nicht die Transaction.
- Der zweite Block hat auch apples im Entwurf geändert, und diese Änderung wurde verworfen.
- __exit__ liest exc_type.__name__ und exc, um zu melden, was schiefging.
- Weil __exit__ None zurückgibt, erreicht der ValueError das try um das with.
Ä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
Tags, die sich immer schließen
Tag(name, out) schreibt Zeilen in die Liste out. Ergänzen Sie __enter__, das "<name>" anhängt und self zurückgibt, und __exit__, das "</name>" anhängt. Die schließende Zeile muss auch dann geschrieben werden, wenn der Block eine Ausnahme auslöst, und die Ausnahme muss den Aufrufer trotzdem erreichen.
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
__exit__ hat vier Parameter: self, exc_type, exc und tb.
Hinweis 2
Python ruft __exit__ auch nach einer Ausnahme auf; das Anhängen dort genügt, ein try ist nicht nötig.
Hinweis 3
Lassen Sie __exit__ nichts zurückgeben: None ist falsch, also wird die Ausnahme nicht unterdrückt.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from types import TracebackType
class Tag:
"""Writes an opening line on entry and the closing line on exit."""
def __init__(self, name: str, out: list[str]) -> None:
self.name = name
self.out = out
def __enter__(self) -> "Tag":
self.out.append(f"<{self.name}>")
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self.out.append(f"</{self.name}>")
if __name__ == "__main__":
lines: list[str] = []
with Tag("ul", lines):
with Tag("li", lines) as item:
item.out.append("tea")
print(lines)
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
from types import TracebackType
class Tag:
"""Writes an opening line on entry and the closing line on exit."""
def __init__(self, name: str, out: list[str]) -> None:
self.name = name
self.out = out
# Add __enter__: append "<name>" to self.out and return self.
# Add __exit__: append "</name>", even when the block raised,
# and do not suppress the exception.
if __name__ == "__main__":
lines: list[str] = []
with Tag("ul", lines):
with Tag("li", lines) as item:
item.out.append("tea")
print(lines)
test_main.py
from main import Tag
def test_as_gives_the_tag():
"""with Tag(...) as t bindet den Tag selbst"""
out = []
tag = Tag("p", out)
with tag as t:
assert t is tag, f"das as-Ziel ist {t!r}, erwartet war der Tag: geben Sie self aus __enter__ zurück"
def test_nested_lines():
"""Verschachtelte Tags öffnen und schließen in der richtigen Reihenfolge"""
out = []
with Tag("ul", out):
with Tag("li", out):
out.append("tea")
assert out == ["<ul>", "<li>", "tea", "</li>", "</ul>"], f"die Zeilen sind {out!r}"
def test_closes_on_error():
"""Die schließende Zeile wird auch bei einer Ausnahme im Block geschrieben"""
out = []
try:
with Tag("p", out):
raise ValueError("boom")
except ValueError:
pass
assert out == ["<p>", "</p>"], f"nach einem Fehler sind die Zeilen {out!r}, erwartet war ['<p>', '</p>']"
def test_does_not_suppress():
"""Eine Ausnahme im Block erreicht weiterhin den Aufrufer"""
try:
with Tag("p", []):
raise ValueError("boom")
except ValueError:
return
raise AssertionError("der ValueError wurde verschluckt: __exit__ darf nicht True zurückgeben")
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
Ein wählerisches Ignore
Vervollständigen Sie __exit__ in Ignore. Es unterdrückt eine Ausnahme nur, wenn sie eine Instanz eines der Typen in self.types ist (Unterklassen zählen mit), und speichert das Ausnahmeobjekt dann in self.caught. Jede andere Ausnahme muss den Aufrufer erreichen, und nach einem sauberen Block bleibt caught None.
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
Bei sauberem Ende ist exc_type None, und issubclass(None, ...) würde scheitern; prüfen Sie das zuerst.
Hinweis 2
issubclass akzeptiert ein Tupel von Klassen: issubclass(exc_type, self.types).
Hinweis 3
if exc_type is not None and issubclass(exc_type, self.types): self.caught = exc; return True. Sonst return False.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from types import TracebackType
class Ignore:
"""Suppresses the given exception types and remembers what it caught."""
def __init__(self, *types: type[BaseException]) -> None:
self.types = types
self.caught: BaseException | None = None
def __enter__(self) -> "Ignore":
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> bool:
if exc_type is not None and issubclass(exc_type, self.types):
self.caught = exc
return True
return False
if __name__ == "__main__":
prices: dict[str, int] = {}
with Ignore(KeyError) as guard:
prices["tea"]
print("caught:", repr(guard.caught))
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
from types import TracebackType
class Ignore:
"""Suppresses the given exception types and remembers what it caught."""
def __init__(self, *types: type[BaseException]) -> None:
self.types = types
self.caught: BaseException | None = None
def __enter__(self) -> "Ignore":
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> bool:
# Suppress (return True) only exceptions of self.types or their
# subclasses, and store the exception in self.caught.
return False
if __name__ == "__main__":
prices: dict[str, int] = {}
with Ignore(KeyError) as guard:
prices["tea"]
print("caught:", repr(guard.caught))
test_main.py
from main import Ignore
def test_suppresses_listed_type():
"""Eine angegebene Ausnahme wird unterdrückt und gespeichert"""
with Ignore(ValueError) as guard:
raise ValueError("bad number")
assert isinstance(guard.caught, ValueError), f"guard.caught ist {guard.caught!r}, erwartet war der ValueError"
def test_subclasses_count():
"""Ignore(LookupError) unterdrückt auch KeyError"""
with Ignore(LookupError) as guard:
{}["missing"]
assert isinstance(guard.caught, KeyError), f"guard.caught ist {guard.caught!r}, erwartet war ein KeyError"
def test_other_types_propagate():
"""Eine nicht angegebene Ausnahme erreicht den Aufrufer"""
try:
with Ignore(ValueError):
raise KeyError("id")
except KeyError:
return
raise AssertionError("Ignore(ValueError) hat einen KeyError verschluckt")
def test_clean_block():
"""Ohne Ausnahme bleibt caught None"""
with Ignore(ValueError) as guard:
pass
assert guard.caught is None, f"guard.caught ist {guard.caught!r} nach einem sauberen Block"
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
__enter__ ohne return
class LogFile:
def __init__(self):
self.lines = []
def __enter__(self):
self.lines.append("start")
def __exit__(self, exc_type, exc, tb):
self.lines.append("end")
with LogFile() as log:
log.lines.append("working")
Was Python ausgibt
AttributeError: 'NoneType' object has no attribute 'lines'Warum, und die Lösung
Der Name nach as erhält, was __enter__ zurückgibt, und eine Funktion ohne return gibt None zurück. Beenden Sie __enter__ mit return self, oder geben Sie die Ressource zurück, die der Block nutzen soll.
__exit__ mit falschen Parametern
class Tag:
def __enter__(self):
return self
def __exit__(self):
print("closing")
with Tag():
print("inside")
Was Python ausgibt
TypeError: Tag.__exit__() takes 1 positional argument but 4 were givenWarum, und die Lösung
Python übergibt __exit__ immer drei Argumente: exc_type, exc und tb, nach einem sauberen Block jeweils None. Schreiben Sie def __exit__(self, exc_type, exc, tb):. Der Fehler kommt erst, nachdem der Block schon gelaufen ist.
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.