Kontextmanager in Python sind die Objekte hinter with. Sie garantieren, dass Aufräumarbeiten tatsächlich ausgeführt werden – auch dann, wenn im Rumpf eine Ausnahme auftritt. Dateien, Datenbankverbindungen, Sperren und Netzwerksitzungen sind die typischen Anwendungsfälle.
Das Problem ohne with
# Ohne Kontextmanager: Die Datei bleibt offen, wenn read() scheitert
f = open("daten.txt")
daten = f.read() # Ausnahme -> close() wird nie erreicht
f.close()
# Mit Kontextmanager: close() läuft immer
with open("daten.txt") as f:
daten = f.read()
Das Muster try/finally leistet dasselbe, wird aber bei mehreren Ressourcen schnell unübersichtlich. Der Kontextmanager ist die kürzere, schwerer falsch zu machende Form.
Eigenen Kontextmanager schreiben
Der klassische Weg ist das Protokoll mit __enter__ und __exit__:
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.dauer = time.perf_counter() - self.start
print(f"Dauer: {self.dauer:.3f}s")
return False # False = Ausnahme weiterreichen
with Timer() as t:
aufwaendige_berechnung()
Der Rückgabewert von __exit__ ist entscheidend: False oder None reicht die Ausnahme weiter, True unterdrückt sie. Unterdrücken sollte man nur nach sehr reiflicher Überlegung – ein verschluckter Fehler ist schwer zu finden.
Der moderne Weg: contextlib
Mit @contextmanager wird eine Generatorfunktion zum Kontextmanager. Alles vor yield ist Aufbau, alles danach ist Abbau:
from contextlib import contextmanager
@contextmanager
def datei_schreiben(pfad):
f = open(pfad, "w", encoding="utf-8")
try:
yield f
f.flush()
finally:
f.close()
with datei_schreiben("/tmp/bericht.txt") as f:
f.write("Inhalt")
Der Generator muss den yield in einem try/finally haben, sonst läuft der Abbau nicht, wenn im Rumpf eine Ausnahme auftritt. Das ist der häufigste Fehler bei dieser Schreibweise.
Mehrere Kontexte und ExitStack
# Mehrere Ressourcen: von innen nach außen abgebaut
with open("a.txt") as a, open("b.txt") as b:
...
# Dynamische Anzahl: ExitStack
from contextlib import ExitStack
with ExitStack() as stack:
dateien = [stack.enter_context(open(p)) for p in pfade]
for f in dateien:
verarbeite(f)
# Alle Dateien werden geschlossen, auch bei Ausnahme
ExitStack ist die Lösung, wenn die Anzahl der Ressourcen erst zur Laufzeit feststeht – etwa beim Öffnen aller Dateien aus einer Liste. In Python 3.10 und neuer gibt es mit with (open("a") as a, open("b") as b): zusätzlich die Klammer-Schreibweise über mehrere Zeilen.
Nützliche Helfer aus contextlib
contextlib.suppress(Exception)– gezielt Ausnahmen ignorieren, lesbarer als ein leererexcept-Block.contextlib.nullcontext()– Platzhalter, wenn ein Kontext optional ist.contextlib.closing(obj)– ruftobj.close(), für Objekte ohne Kontextmanager-Unterstützung.contextlib.redirect_stdout(io.StringIO())– Ausgaben abfangen, nützlich in Tests.contextlib.AsyncExitStackund@asynccontextmanager– dieselben Muster fürasync with.
Warum sich das in der Praxis lohnt
Kontextmanager sind mehr als Syntaxzucker: Sie verlagern Aufräumpflichten an eine Stelle, an der sie strukturell garantiert sind. Das ist besonders wertvoll bei Ressourcen mit knappem Kontingent – Datenbankverbindungen aus einem Pool, Dateideskriptoren, Sperren. Ein vergessenes close() in einem seltenen Fehlerpfad führt sonst zu einem Leck, das erst nach Tagen oder Wochen sichtbar wird und dann schwer zu lokalisieren ist.
Zwei Regeln aus der Praxis: Ein Kontextmanager sollte eine Sache verwalten – Aufbau, Nutzung und Abbau gehören zusammen. Und der Abbau darf keine Ausnahme werfen, die die ursprüngliche verdeckt; Fehler beim Aufräumen gehören protokolliert, nicht weitergereicht.
Verwandte Grundlagen: Python: Exceptions, Python: Dateien lesen und schreiben, Python: Funktionen und Module, Speicheranalyse.