Lekcje modułu (3/4)
Menedżery kontekstu: with oraz __enter__/__exit__
A context manager (menedżer kontekstu) to obiekt używany z instrukcją with. Gwarantuje on, że określone operacje przygotowawcze i porządkowe (setup/cleanup) zawsze się odbędą, nawet w przypadku wystąpienia błędów. Klasycznym przykładem jest otwieranie i zamykanie pliku.
Schemat with
with open("note.txt", "w") as f:
f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezioneW prostych słowach: "z zasobem X otwartym jako f, zrób to. Po zakończeniu (cokolwiek się stanie), zamknij X". Bez with musiałbyś za każdym razem pisać blok try/finally.
Więcej niż jeden na raz
with open("a.txt") as f, open("b.txt") as g:
dati_a = f.read()
dati_b = g.read()Pisanie własnego menedżera kontekstu (klasa)
Musisz zdefiniować dwie metody specjalne (dunder): __enter__ i __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)jest wywoływana przy wejściu do bloku; zwrócona wartość to ta, którą otrzymujesz po słowieas.__exit__(self, exc_type, exc_value, tb)jest wywoływana przy wyjściu (zawsze). Jeśli zwróciTrue, ewentualne wyjątki zostaną stłumione (zignorowane).
Krótsza droga: @contextmanager
contextlib.contextmanager pozwala zapisać menedżer kontekstu jako funkcję generatora:
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))Kod przed yield odpowiada metodzie __enter__, a kod po yield odpowiada __exit__. Blok try/finally gwarantuje czyszczenie nawet wtedy, gdy wystąpią błędy.
suppress: ignorowanie wyjątku
from contextlib import suppress
with suppress(FileNotFoundError):
open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propagaPułapka: with tylko dla obiektów, które to obsługują
x = 42
with x: # AttributeError: __enter__
...Pisanie menedżerów kontekstu z @contextmanager
Moduł contextlib z biblioteki standardowej udostępnia bardzo wygodny dekorator @contextmanager do tworzenia menedżerów kontekstu za pomocą funkcji generatora, zamiast pisania rozbudowanych klas implementujących metody __enter__ i __exit__. Wystarczy napisać funkcję generatora zawierającą pojedynczą instrukcję yield:
from contextlib import contextmanager
@contextmanager
def manage_resource():
# __enter__ code
yield resource
# __exit__ codeSpróbuj sam
Użyj contextlib.suppress, aby spróbować wykonać int('non-numero'), ignorując błąd ValueError. Po bloku przypisz `ok = True`. Oceń `ok`.
Pokaż wskazówkę
suppress(ValueError) przechwytuje tylko ValueError.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie powtórzeniowe
Napisz menedżer kontekstu `collect` za pomocą @contextmanager, który zwraca (yield) pustą listę, a przy wyjściu konwertuje ją na krotkę (wypisz ją). Użyj go, aby dodać wartości 1, 2, 3 do listy wewnątrz bloku. Na końcu oceń `len(collection)`, gdzie `collection` to zwrócona lista.
Pokaż wskazówkę
Użyj yield items, aby udostępnić listę wewnątrz bloku with.
Rozwiązanie dostępne po 3 próbach
Dodatkowe wyzwanie
Napisz własny menedżer kontekstu, używając dekoratora `@contextmanager` z biblioteki `contextlib`. Menedżer kontekstu musi nazywać się `mock_session`. Przy wejściu musi on dodać `'start'` do globalnej listy `log_list = []`, przekazać sterowanie za pomocą `yield`, a przy wyjściu dodać `'stop'`. Na koniec oceń `log_list` po użyciu menedżera kontekstu w bloku `with mock_session():`.
Pokaż wskazówkę
Użyj yield wewnątrz mock_session, aby wstrzymać wykonywanie przed dodaniem 'stop'.
Rozwiązanie dostępne po 3 próbach