Контекстные менеджеры: with, __enter__ и __exit__

Вопрос про надёжную работу с ресурсами: как with гарантирует закрытие файла или соединения.

Контекстный менеджер — объект с методами __enter__ (вход в блок) и __exit__ (выход), которые оператор with вызывает автоматически.

Вопрос: зачем нужен with?

Чёткий ответ. with гарантирует, что завершающий код (закрытие файла, освобождение блокировки) выполнится всегда — даже если внутри блока возникнет исключение. Это замена ручному try/finally.

class Resource:
    def __enter__(self):
        print("открыли ресурс")
        return self

    def __exit__(self, exc_type, exc_val, tb):
        print("закрыли ресурс")
        return False           # исключение не подавляем

with Resource():
    print("работаем с ресурсом")

Вывод:

открыли ресурс
работаем с ресурсом
закрыли ресурс

Главное свойство: закрытие при исключении

Даже если внутри with вылетит ошибка, __exit__ всё равно вызовется — ресурс будет закрыт. В этом вся ценность.

class Resource:
    def __enter__(self):
        print("открыли")
        return self
    def __exit__(self, exc_type, exc_val, tb):
        print("закрыли (даже при ошибке)")
        return False

try:
    with Resource():
        print("работаем")
        raise ValueError("сбой внутри блока")
except ValueError as e:
    print("поймали снаружи:", e)

Вывод:

открыли
работаем
закрыли (даже при ошибке)
поймали снаружи: сбой внутри блока

Порядок важен: ресурс закрылся до того, как исключение поднялось наружу. Вернув False из __exit__, мы не подавляем исключение — оно нормально ловится снаружи.

Короткий способ: contextlib.contextmanager

Целый класс не обязателен — можно сделать менеджер из генератора. Код до yield — это вход, после — выход.

from contextlib import contextmanager

@contextmanager
def tag(name):
    print(f"<{name}>")
    yield
    print(f"</{name}>")

with tag("b"):
    print("жирный текст")

Вывод:

<b>
жирный текст
</b>

Итог

  • with вызывает __enter__ на входе и __exit__ на выходе — всегда, даже при исключении.
  • Это надёжная замена try/finally для управления ресурсами (файлы, блокировки, соединения).
  • Быстрый способ создать менеджер — генератор с @contextmanager и одним yield.
Проверьте себя
1. Какие методы реализует контекстный менеджер?
A__init__ и __del__
B__enter__ и __exit__
C__iter__ и __next__
D__str__ и __repr__
2. Вызовется ли __exit__, если внутри with возникнет исключение?
AНет
BДа — ресурс закрывается даже при ошибке
CТолько если поймать исключение
DЗависит от ОС
3. Что возвращает __exit__, чтобы НЕ подавлять исключение?
ATrue
BFalse
CNone всегда подавляет
Dсам объект
Поддержать проект