Урок курса

Контекстный менеджер и оператор with в Python

Python для профессионалов

Оператор with задаёт участок кода с гарантированным входом и выходом. Самый знакомый пример — файл: он будет закрыт и при успехе, и при исключении.

with open("report.txt", "w", encoding="utf-8") as file:
    file.write("ready")

Объект справа от with реализует протокол контекстного менеджера. Метод __enter__ подготавливает ресурс и возвращает значение после as. Метод __exit__ получает сведения об исключении и освобождает ресурс. Это структурированный аналог try/finally, который сложнее забыть.

Собственный контекстный менеджер

class Timer:
    def __enter__(self):
        from time import perf_counter
        self.started = perf_counter()
        return self

    def __exit__(self, exc_type, exc, traceback):
        from time import perf_counter
        self.elapsed = perf_counter() - self.started
        return False

with Timer() as timer:
    calculate()

print(timer.elapsed)

Если __exit__ возвращает истинное значение, исключение считается обработанным. Обычно возвращают False или None, чтобы не проглотить ошибку случайно. Освобождение ресурса должно выполняться даже при частично завершённой операции.

contextlib и область применения

Для простых случаев класс не обязателен. Декоратор contextlib.contextmanager превращает генератор с одним yield в контекстный менеджер: код до yield выполняет вход, блок finally после него — выход.

Контекстные менеджеры подходят для файлов, транзакций, временной смены настроек, блокировок и сетевых соединений. Они не делают операцию атомарной сами по себе: например, менеджер транзакции должен явно вызвать commit или rollback по контракту конкретной библиотеки.

Главный критерий — ресурс или состояние имеют парные действия «получить/освободить». Тогда with фиксирует границы владения прямо в структуре программы и уменьшает риск утечек.

Cleanup срабатывает даже при исключении

Минимальный контекстный менеджер открывает ресурс до yield и освобождает его в finally. Покажем поведение на списке событий, чтобы запуск примера не требовал файлов или сетевого сервиса. Исключение внутри with продолжает распространяться, если менеджер его не подавляет.

from contextlib import contextmanager
events = []
@contextmanager
def resource():
    events.append("open")
    try:
        yield "connection"
    finally:
        events.append("close")
try:
    with resource() as value:
        assert value == "connection"
        raise ValueError("Учебная ошибка")
except ValueError:
    events.append("caught")
print(events)
assert events == ["open", "close", "caught"]

Прежде чем внешний except обработает ошибку, выполняется finally менеджера. Менеджер управляет освобождением, но не делает любую операцию атомарной и не откатывает автоматически изменение базы. Транзакцию и повторяемость нужно проектировать отдельно. Контракт contextmanager в Python.

Продолжите разбор: Декораторы в Python: функции, @ и параметры.

Задача 1

Какое значение exit обычно возвращает, если исключение не нужно подавлять?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку