Перейти до основного вмісту
Модуль 9 · Урок 3 із 435/36 у курсі~12 min
Модульні уроки (3/4)

Менеджер контексту: з і __enter__/__exit__

Менеджер контексту – це об’єкт, який ви використовуєте з with. Це гарантує це певні операції налаштування/очищення виконуються завжди, навіть якщо виникають помилки. Канонічний приклад: відкриття та закриття файлу.

Шаблон with

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

Простою англійською: «з ресурсом X, відкритим як f, зробіть це. Коли закінчите (що б не сталося), закрийте X." Без with вам довелося б писати try/finally кожного разу.

Більш ніж один одночасно

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

Написання власного контекстного менеджера (класу)

Вам потрібно визначити два методи dunder: __enter__ та __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) викликається при вході в блок; повертається значення, яке ви отримуєте після as.
  • __exit__(self, exc_type, exc_value, tb) викликається при виході (завжди). Якщо він повертає True, будь-які винятки пригнічуються.

Ярлик: @contextmanager

contextlib.contextmanager дозволяє написати менеджер контексту як функція генератора:

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))

Код перед yield — __enter__, код після — __exit__. try/finally гарантує очищення навіть у разі виникнення помилок.

придушити: ігнорувати виняток

Python
from contextlib import suppress

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

Підводний камінь: with лише для об’єктів, які його підтримують

Python
x = 42
with x:  # AttributeError: __enter__
    ...

Написання контекстних менеджерів за допомогою @contextmanager

Модуль contextlib стандартної бібліотеки експортує надзвичайно зручний декоратор під назвою @contextmanager для створення менеджерів контексту за допомогою функцій генератора замість написання докладних класів, що реалізують методи __enter__ та __exit__. Ви просто пишете функцію генератора, яка використовує один yield:

Python
from contextlib import contextmanager

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

Спробуйте самі

вправи#python.m9.l3.e1
Спроби: 0Завантаження…

Використовуйте contextlib.suppress, щоб спробувати int('non-numero'), ігноруючи ValueError. Після блоку призначте `ok = True`. Оцініть `ок`.

Завантаження редактора…
Показати підказку

suppress(ValueError) ковтає лише ValueError.

Рішення доступне після 3 спроб

Повторна вправа

вправи#python.m9.l3.e2
Спроби: 0Завантаження…

Напишіть контекстний менеджер `collect` за допомогою @contextmanager, який дає порожній список і після виходу перетворює його на кортеж (надрукуйте його). Використовуйте його, щоб додати 1, 2, 3 до списку всередині блоку. Наприкінці обчисліть `len(collection)`, де `collection` — це отриманий список.

Завантаження редактора…
Показати підказку

yield елементи, щоб відкрити список усередині блоку with.

Рішення доступне після 3 спроб

Додатковий виклик

вправи#python.m9.l3.e3
Спроби: 0Завантаження…

Write a custom context manager using the `@contextmanager` decorator from `contextlib`. The context manager must be named `mock_session`. Upon entry, it must append `'start'` to a global list `log_list = []`, yield control using `yield`, and upon exit append `'stop'`. Finally, evaluate `log_list` after using the context manager with `with mock_session():`.

Завантаження редактора…
Показати підказку

Використовуйте yield всередині mock_session, щоб призупинити виконання перед додаванням 'stop'.

Рішення доступне після 3 спроб