Context Manager in Python: Ressourcen sicher verwalten mit with

Mit dem with-Statement gibst du Dateien, Verbindungen und andere Ressourcen zuverlässig wieder frei – auch wenn ein Fehler auftritt. So schreibst du eigene Context Manager.

Teilen

Du kennst das with-Statement wahrscheinlich vom Öffnen einer Datei: with open(...) as f:. Aber was passiert dahinter eigentlich? Hinter dieser eleganten Syntax steckt ein mächtiges Konzept namens Context Manager. In diesem Beitrag schauen wir uns an, welches Problem Context Manager lösen, wie sie intern funktionieren und wie du eigene schreibst – sowohl mit einer Klasse als auch mit nur ein paar Zeilen über das contextlib-Modul.

Das Problem: Ressourcen müssen wieder freigegeben werden

Viele Ressourcen, die du im Code anforderst, musst du danach wieder sauber schließen: Dateien, Datenbankverbindungen, Netzwerk-Sockets oder Locks. Vergisst du das, riskierst du Speicherlecks, gesperrte Dateien oder verlorene Daten. Der naive Ansatz sieht so aus:

f = open("daten.txt", "w")
f.write("Hallo Welt")
f.close()  # Wird das hier wirklich immer erreicht?

Das Problem: Wenn zwischen open und close ein Fehler auftritt, wird close() nie aufgerufen. Die Datei bleibt geöffnet. Du könntest das mit try/finally absichern – aber genau diese Wiederholung nimmt dir der Context Manager ab.

Die Lösung: Das with-Statement

Mit with wird die Ressource automatisch freigegeben, sobald der Block verlassen wird – egal ob normal oder durch eine Exception:

with open("daten.txt", "w") as f:
    f.write("Hallo Welt")
# Hier ist die Datei garantiert geschlossen,
# selbst wenn write() einen Fehler ausgelöst hätte.

Das as f bindet das Ergebnis an einen Namen. Der entscheidende Vorteil: Du kannst dich nicht mehr "vergessen" – das Aufräumen ist garantiert. Genau dafür sind Context Manager da.

Wie funktioniert ein Context Manager intern?

Ein Context Manager ist jedes Objekt, das zwei spezielle Methoden besitzt: __enter__ und __exit__. Das with-Statement ruft sie automatisch auf:

  • __enter__() wird beim Betreten des Blocks aufgerufen. Der Rückgabewert landet hinter as.
  • __exit__() wird beim Verlassen aufgerufen – immer, auch bei einer Exception.

Vereinfacht ist with also nur Syntax-Zucker für folgendes Muster:

manager = open("daten.txt", "w")
f = manager.__enter__()
try:
    f.write("Hallo Welt")
finally:
    manager.__exit__(None, None, None)

Du siehst: Das finally sorgt dafür, dass __exit__ garantiert läuft. Genau dieses Boilerplate musst du dank with nie selbst schreiben.

Eigene Context Manager mit einer Klasse schreiben

Sobald du eine Aktion immer mit einem passenden Gegenstück abschließen willst, lohnt sich ein eigener Context Manager. Ein klassisches Beispiel ist ein kleiner Timer, der misst, wie lange ein Block dauert:

import time

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self  # landet hinter "as"

    def __exit__(self, exc_type, exc_value, traceback):
        dauer = time.perf_counter() - self.start
        print(f"Dauer: {dauer:.3f} Sekunden")
        return False  # Exceptions nicht unterdrücken

with Timer():
    summe = sum(i * i for i in range(1_000_000))
    print(f"Ergebnis: {summe}")

Die drei Parameter von __exit__ beschreiben eine eventuell aufgetretene Exception. Gibst du False (oder gar nichts) zurück, wird die Exception danach normal weitergereicht. Nur ein return True würde sie schlucken – das solltest du nur ganz bewusst tun.

Context Manager mit contextlib vereinfachen

Eine ganze Klasse für zwei Methoden zu schreiben, ist oft mehr Aufwand als nötig. Das Modul contextlib bietet den Decorator @contextmanager, mit dem du einen Context Manager als Generatorfunktion schreibst. Alles vor yield ist das "Setup", alles danach das "Aufräumen":

from contextlib import contextmanager

@contextmanager
def timer():
    start = time.perf_counter()
    try:
        yield  # hier läuft der with-Block
    finally:
        dauer = time.perf_counter() - start
        print(f"Dauer: {dauer:.3f} Sekunden")

with timer():
    summe = sum(i * i for i in range(1_000_000))

Das try/finally ist wichtig: Es stellt sicher, dass dein Aufräum-Code auch dann läuft, wenn im with-Block eine Exception fliegt. Brauchst du einen Wert hinter as, gibst du ihn einfach per yield wert heraus.

Mehrere Context Manager gleichzeitig nutzen

Du kannst mehrere Ressourcen in einem einzigen with verwalten – zum Beispiel beim Kopieren von einer Datei in eine andere:

with open("quelle.txt") as ein, open("ziel.txt", "w") as aus:
    for zeile in ein:
        aus.write(zeile.upper())
# Beide Dateien werden zuverlässig geschlossen.

Beide werden korrekt geschlossen, selbst wenn beim Schreiben ein Fehler auftritt. Bei sehr vielen oder dynamisch erzeugten Ressourcen hilft dir zusätzlich contextlib.ExitStack, das beliebig viele Context Manager sauber verschachtelt.

Fazit

Context Manager sind eines der elegantesten Werkzeuge in Python, um Ressourcen sicher zu verwalten. Statt fehleranfälligem try/finally überall im Code zu wiederholen, kapselst du die Aufräum-Logik einmal an einer Stelle. Für einfache Fälle reicht oft schon das eingebaute with open(...); eigene Manager schreibst du entweder als Klasse mit __enter__/__exit__ oder noch kompakter mit @contextmanager aus contextlib. Immer wenn du eine Aktion zuverlässig mit einem Gegenstück abschließen musst – öffnen und schließen, sperren und freigeben, starten und stoppen – ist ein Context Manager die richtige Wahl. Probier es beim nächsten Mal aus, wenn du irgendwo ein close() oder finally schreibst.