Die Geschichte – in Python wird zur Verwaltung von Ressourcen (Dateien, Verbindungen, Transaktionen) die with-Konstruktion verwendet, die auf dem Protokoll der Kontextmanager basiert (enter, exit). Für einfache Fälle ist es überflüssig, eine ganze Klasse zu schreiben, daher wurde der Dekorator @contextmanager (Modul contextlib) vorgeschlagen, der es ermöglicht, Ressourcenmanager als Generatoren zu definieren.
Problem – Manuell Ressourcen freizugeben oder zu schließen, ist unpraktisch, der Code wird anfällig für Fehler (z. B. Datei vergessen zu schließen). Außerdem möchte man für einfache Dinge (z. B. temporäre Änderung des Verzeichnisses oder stdout) nicht eine separate Klasse mit zwei Methoden schreiben.
Lösung – Verwenden Sie @contextmanager, um den "Anfang" und "Ende" der Ressourcennutzung prägnant zu beschreiben und dabei Ausnahmen und die Freigabe garantiert zu verarbeiten.
Beispielcode:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Hauptmerkmale:
Kann man den yield aus @contextmanager so konfigurieren, dass mehrere Objekte (z. B. über ein Tuple) zurückgegeben werden?
Ja, das ist möglich und sogar praktisch, um eine "Gruppe" verwandter Ressourcen zurückzugeben.
Beispielcode:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
Was passiert, wenn nach yield eine Ausnahme ausgelöst wird – wird die Ressource geschlossen?
Ja, der finally-Block wird auf jeden Fall ausgeführt, selbst wenn im Code innerhalb des with ein Fehler/ eine Ausnahme auftritt.
Kann @contextmanager eine vollständige Klasse eines Kontextmanagers mit enter/exit ersetzen?
In den meisten trivialen Fällen – ja, für komplexere mit verschachtelten Zuständen oder Vererbung ist es bequemer, über eine Klasse zu arbeiten.
Manuell eine Datei öffnen und schließen:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Vorteile:
Nachteile:
Verwendung von @contextmanager für temporäre Änderungen des Arbeitsverzeichnisses oder zum Öffnen einer Datei (oder von Umgebungsvariablen):
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Vorteile:
Nachteile: