Lektionen des Moduls (3/4)
Context Manager: with und __enter__/__exit__
Ein Kontextmanager (context manager) ist ein Objekt, das du mit with verwendest. Er garantiert, dass bestimmte Setup-/Cleanup-Operationen immer stattfinden, selbst wenn Fehler auftreten. Das kanonische Beispiel: Öffnen und Schließen einer Datei.
Das with-Muster
with open("note.txt", "w") as f:
f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezioneEinfach ausgedrückt: "mit Ressource X geöffnet als f, tue dies. Wenn fertig (was auch immer passiert), schließe X." Ohne with müsstest du jedes Mal ein try/finally schreiben.
Mehrere gleichzeitig
with open("a.txt") as f, open("b.txt") as g:
dati_a = f.read()
dati_b = g.read()Einen eigenen Kontextmanager schreiben (Klasse)
Du musst zwei Dunder-Methoden definieren: __enter__ und __exit__.
class Timer:
def __enter__(self):
import time
self.t0 = time.perf_counter()
return self # è l'oggetto che finisce dopo "as"
def __exit__(self, exc_type, exc_value, traceback):
import time
self.elapsed = time.perf_counter() - self.t0
# ritornare True ingoia l'eccezione; di solito non lo vuoi
return False
with Timer() as t:
sum(range(10**6))
t.elapsed # quanto è durato__enter__(self)wird beim Betreten des Blocks aufgerufen; der zurückgegebene Wert ist das, was du nachaserhältst.__exit__(self, exc_type, exc_value, tb)wird beim Verlassen aufgerufen (immer). Wenn sieTruezurückgibt, werden alle Ausnahmen unterdrückt.
Die Abkürzung: @contextmanager
contextlib.contextmanager ermöglicht es dir, einen Kontextmanager als Generatorfunktion zu schreiben:
from contextlib import contextmanager
@contextmanager
def timer():
import time
t0 = time.perf_counter()
try:
yield # qui sta il blocco "with"
finally:
elapsed = time.perf_counter() - t0
print(f"durata: {elapsed:.4f}s")
with timer():
sum(range(10**6))Der Code vor yield entspricht __enter__, der Code danach __exit__. Das try/finally garantiert das Cleanup, selbst wenn Fehler auftreten.
suppress: Eine Ausnahme ignorieren
from contextlib import suppress
with suppress(FileNotFoundError):
open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propagaFalle: with nur für Objekte, die es unterstützen
x = 42
with x: # AttributeError: __enter__
...Kontextmanager mit @contextmanager schreiben
Das Modul contextlib der Standardbibliothek exportiert einen sehr praktischen Dekorator namens @contextmanager, um Kontextmanager mithilfe von Generatorfunktionen zu erstellen, anstatt wortreiche Klassen zu schreiben, die die Methoden __enter__ und __exit__ implementieren. Du schreibst einfach eine Generatorfunktion, die ein einziges yield verwendet:
from contextlib import contextmanager
@contextmanager
def manage_resource():
# __enter__ code
yield resource
# __exit__ codeProbiere es aus
Verwende contextlib.suppress, um int('non-numero') auszuprobieren und dabei ValueError zu ignorieren. Weise nach dem Block `ok = True` zu. Evaluiere `ok`.
Hinweis anzeigen
suppress(ValueError) fängt nur ValueError ab.
Lösung nach 3 Versuchen verfügbar
Wiederholungsübung
Schreibe einen Kontextmanager `collect` mit @contextmanager, der eine leere Liste zurückgibt (yield) und diese beim Verlassen in ein Tupel umwandelt (gib es aus). Verwende ihn, um 1, 2, 3 an die Liste im Block anzuhängen. Evaluiere am Ende `len(collection)`, wobei `collection` die zurückgegebene Liste ist.
Hinweis anzeigen
Verwende yield items, um die Liste im with-Block bereitzustellen.
Lösung nach 3 Versuchen verfügbar
Zusätzliche Herausforderung
Schreibe einen benutzerdefinierten Kontextmanager mit dem Dekorator `@contextmanager` aus `contextlib`. Der Kontextmanager muss den Namen `mock_session` tragen. Beim Betreten muss er `'start'` an eine globale Liste `log_list = []` anhängen, die Steuerung mit `yield` abgeben und beim Verlassen `'stop'` anhängen. Evaluiere schließlich `log_list`, nachdem du den Kontextmanager mit `with mock_session():` verwendet hast.
Hinweis anzeigen
Verwende yield innerhalb von mock_session, um die Ausführung auszusetzen, bevor 'stop' angehängt wird.
Lösung nach 3 Versuchen verfügbar