Контекстные менеджеры: 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сам объект