Przejdź do głównej treści
eLearner.app
Moduł 9 · Lekcja 3 z 435/36 w kursie~12 min
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

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

W 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

Python
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__.

Python
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łowie as.
  • __exit__(self, exc_type, exc_value, tb) jest wywoływana przy wyjściu (zawsze). Jeśli zwróci True, ewentualne wyjątki zostaną stłumione (zignorowane).

Krótsza droga: @contextmanager

contextlib.contextmanager pozwala zapisać menedżer kontekstu jako funkcję generatora:

Python
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

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

Pułapka: with tylko dla obiektów, które to obsługują

Python
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:

Python
from contextlib import contextmanager

@contextmanager
def manage_resource():
    # __enter__ code
    yield resource
    # __exit__ code

Spróbuj sam

Ćwiczenie#python.m9.l3.e1
Próby: 0Ładowanie...

Użyj contextlib.suppress, aby spróbować wykonać int('non-numero'), ignorując błąd ValueError. Po bloku przypisz `ok = True`. Oceń `ok`.

Ładowanie edytora...
Pokaż wskazówkę

suppress(ValueError) przechwytuje tylko ValueError.

Rozwiązanie dostępne po 3 próbach

Ćwiczenie powtórzeniowe

Ćwiczenie#python.m9.l3.e2
Próby: 0Ładowanie...

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.

Ładowanie edytora...
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

Ćwiczenie#python.m9.l3.e3
Próby: 0Ładowanie...

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():`.

Ładowanie edytora...
Pokaż wskazówkę

Użyj yield wewnątrz mock_session, aby wstrzymać wykonywanie przed dodaniem 'stop'.

Rozwiązanie dostępne po 3 próbach