Kontextmanager ermöglichen die Automatisierung und Kontrolle der Ausführung von Codeblöcken mit garantierter Ressourcenfinalisierung — zum Beispiel das automatische Schließen von Dateien, das Freigeben von Transaktionsmanagern in der Datenbank oder das Sperren/Entsperren von Threads. In der Python-Standardbibliothek wurde dieses Paradigma eingeführt, um eine sichere und bequeme Handhabung externer Ressourcen zu gewährleisten, ohne manuell den Abschluss der Arbeit überwachen zu müssen.
Geschichte:
Vor der Einführung von Kontextmanagern war es erforderlich, die close()/release()-Methoden explizit aufzurufen, um Ressourcen freizugeben, was zu Fehlern führte, wenn Ausnahmen auftraten. Mit der Einführung der Konstruktion with ... as ...: ermöglichte Python eine explizite Definition des "Gültigkeitsbereichs der Ressource", indem es die Methoden zur Initialisierung und zum Abschluss der Arbeit automatisiert aufruft.
Problem:
Manuelles Management der "Schließung" von Ressourcen ist gefährlich — wenn man das close() (oder release()) vergisst, bleiben Ressourcen bis zum Abschluss des Prozesses oder sogar für immer blockiert. Dies ist besonders kritisch bei der Arbeit mit Dateien, Netzwerkverbindungen und Transaktionen in Datenbanken.
Lösung:
Kontextmanager werden durch magische Methoden enter() und exit() implementiert. Beim Eintritt in den with-Block ruft Python enter auf und beim Austritt exit, selbst wenn innerhalb des Blocks eine Ausnahme aufgetreten ist.
Beispielcode:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # Ausnahme kann unterdrückt werden, return True, normalerweise return False with ManagedFile('demo.txt') as f: f.write('Hallo, Welt! ') # Datei wird garantiert geschlossen
Schlüsseleigenschaften:
Kann derselbe Kontextmanager mehrere Male hintereinander in verschiedenen with-Blocks verwendet werden?
Nein: normalerweise wird die Ressource in enter geöffnet, in exit freigegeben, und das Objekt wird ungültig für die Wiederverwendung. Besser ist es, für jeden with-Block ein neues Objekt zu erstellen.
Was passiert, wenn mehrere Ressourcen in einem with-Block verwendet werden müssen?
Man kann die Variablen durch Kommas trennen:
with open('a.txt') as fa, open('b.txt') as fb: ...
oder contextlib.ExitStack für komplexe Fälle verwenden.
Wie unterscheidet sich die Implementierung eines Kontextmanagers als Klasse mit enter/exit von der Implementierung als Generator mit dem @contextmanager-Dekorator?
Der @contextmanager-Dekorator aus dem Modul contextlib ermöglicht eine einfachere Implementierung des Managers über yield:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
Der Entwickler öffnet eine Datei mit open(), schreibt darin und vergisst, close() aufzurufen — die Datei kann offen bleiben (insbesondere bei Fehlern), die Daten werden nicht gespeichert.
Vorteile:
Nachteile:
Überall wird with open() als Standard verwendet (oder als benutzerdefinierte verwaltete Ressource), der Kontextmanager implementiert korrekt die Freigabe der Ressource.
Vorteile:
Nachteile: