Der with-Block in Python: Kontextmanager verstehen und selbst schreiben
Warum du Dateien am besten mit with öffnest, wie Kontextmanager hinter den Kulissen funktionieren und wie du mit __enter__/__exit__ und @contextmanager eigene baust.
Du hast in Python fast sicher schon eine Datei so geöffnet:
with open("daten.txt") as f:
inhalt = f.read()
Das kleine Schlüsselwort with ist einer der elegantesten Bausteine der Sprache – und viele nutzen es, ohne genau zu wissen, was dahintersteckt. In diesem Beitrag klären wir das: Was ist ein Kontextmanager, warum spart er dir Fehler, und wie baust du dir eigene? Danach schreibst du with-Blöcke mit einem ganz anderen Verständnis.
Das Problem, das with löst
Stell dir vor, du öffnest eine Datei ohne with:
f = open("daten.txt")
inhalt = f.read()
f.close()
Sieht harmlos aus – aber wenn zwischen read() und close() ein Fehler auftritt, wird close() nie erreicht. Die Datei bleibt offen, das Betriebssystem hält Ressourcen fest, und bei vielen Dateien läufst du irgendwann gegen ein Limit. Du müsstest also eigentlich schreiben:
f = open("daten.txt")
try:
inhalt = f.read()
finally:
f.close()
Genau dieses try/finally-Muster nimmt dir der Kontextmanager ab. with garantiert, dass am Ende des Blocks aufgeräumt wird – egal ob alles glattlief oder eine Exception geflogen ist.
Wie ein Kontextmanager funktioniert
Hinter with steckt ein einfaches Protokoll: Ein Objekt ist ein Kontextmanager, wenn es zwei Methoden hat – __enter__ und __exit__. Beim Betreten des Blocks ruft Python __enter__() auf; der Rückgabewert landet hinter dem as. Beim Verlassen – auch bei einem Fehler – ruft Python __exit__() auf. So sieht ein selbstgebauter Kontextmanager aus:
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
dauer = time.perf_counter() - self.start
print(f"Dauer: {dauer:.3f} Sekunden")
with Timer():
summe = sum(range(10_000_000))
Die drei Parameter von __exit__ beschreiben eine eventuell aufgetretene Exception. Gibst du True zurück, gilt der Fehler als behandelt und wird unterdrückt; gibst du nichts (also None) zurück, reicht Python die Exception normal weiter. Das ist ein mächtiges Detail – aber unterdrücke Fehler nur, wenn du wirklich weißt, was du tust.
Der schnellere Weg: @contextmanager
Eine ganze Klasse für einen kleinen Auf- und Abbau zu schreiben, ist oft zu viel. Das Modul contextlib bietet den Dekorator @contextmanager, mit dem du einen Kontextmanager als Funktion mit einem einzigen yield schreibst:
from contextlib import contextmanager
@contextmanager
def timer():
import time
start = time.perf_counter()
try:
yield
finally:
print(f"Dauer: {time.perf_counter() - start:.3f} s")
with timer():
summe = sum(range(10_000_000))
Alles vor dem yield entspricht __enter__, alles danach __exit__. Das finally sorgt dafür, dass auch bei einem Fehler aufgeräumt wird. Kürzer und lesbarer geht es kaum.
Noch tiefer einsteigen
Kontextmanager können weit mehr als Dateien schließen: Datenbank-Transaktionen sauber abschließen, temporäre Verzeichnisse wieder löschen, mehrere Ressourcen mit ExitStack verschachteln oder mit suppress gezielt bestimmte Fehler ignorieren. Wenn du das komplette Werkzeugset des contextlib-Moduls kennenlernen willst, empfehle ich dir den ausführlichen englischsprachigen Deep Dive unseres Schwester-Blogs pykit.org: The with Statement Deconstructed: A Practical Deep Dive into Python's contextlib. Dort werden ExitStack, suppress, redirect_stdout und nullcontext mit lauffähigen Beispielen erklärt.
Fazit
Der with-Block ist kein Zauber, sondern ein klares Protokoll aus __enter__ und __exit__. Sobald du das verstanden hast, schreibst du robusteren Code, der Ressourcen zuverlässig freigibt – und mit @contextmanager baust du dir eigene Helfer in wenigen Zeilen. Fang bei deiner nächsten Datei-, Timer- oder Transaktionslogik damit an, und du wirst try/finally selten vermissen.