Перейти к основному содержимому
Модуль 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`. Оцените «ОК».

Загрузка редактора…
Показать подсказку

подавление (ValueError) поглощает только ValueError.

Решение доступно после 3 попыток

Обзор упражнения

Упражнение#python.m9.l3.e2
Попыток: 0Загрузка…

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

Загрузка редактора…
Показать подсказку

выведите элементы, чтобы предоставить список внутри блока 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():`.

Загрузка редактора…
Показать подсказку

Используйте выход внутри Mock_session, чтобы приостановить выполнение перед добавлением «stop».

Решение доступно после 3 попыток