__enter__/__exit__, а также как функцию‑генератор с декоратором contextlib.contextmanager, где yield разделяет код инициализации и финализации.
__enter__/__exit__, а также как функцию‑генератор с декоратором contextlib.contextmanager, где yield разделяет код инициализации и финализации.
Классический способ: класс с __enter__ и __exit__
Контекстный менеджер — это объект, реализующий методы __enter__(self) и __exit__(self, exc_type, exc, tb). Конструкция with вызывает эти методы по протоколу:
перед входом в блок with вызывается __enter__; его результат присваивается переменной после as;
при выходе из блока (нормальном или по исключению) вызывается __exit__;
если __exit__ возвращает True, исключение подавляется.
class managed_resource:
def __init__(self, name):
self.name = name
self.handle = None
def __enter__(self):
print(f"acquire {self.name}")
self.handle = open(self.name, "w")
return self.handle
def __exit__(self, exc_type, exc, tb):
print(f"release {self.name}")
if self.handle:
self.handle.close()
# вернём False, чтобы не подавлять исключения
return False
with managed_resource("log.txt") as f:
f.write("hello")Этот способ удобен, когда вам нужен полноценный объект с состоянием и, возможно, повторным использованием.
Функциональный способ: contextlib.contextmanager
Второй способ — описать контекстный менеджер как генератор и обернуть его @contextlib.contextmanager. В этом случае:
код до yield соответствует __enter__;
значение, переданное через yield, — это то, что попадёт в as;
код после yield — это __exit__; он выполнится всегда, даже при исключении.
from contextlib import contextmanager
@contextmanager
def managed_resource(name):
print(f"acquire {name}")
f = open(name, "w")
try:
yield f # сюда попадает переменная после 'as'
finally:
print(f"release {name}")
f.close()
with managed_resource("log.txt") as f:
f.write("hello")Такой способ чаще используют, когда нужен лёгкий одноразовый менеджер без отдельного класса, особенно когда логика инициализации/финализации компактна.
Отличия и выбор
класс даёт больше контроля, возможности наследования, хранения состояния и тестирования по отдельности;
генераторный подход с @contextmanager — более лаконичен для простых кейсов, но скрывает детали протокола и чуть сложнее отлаживается при большой логике.
Формулировка для собеседования
Свой контекстный менеджер можно реализовать классом с методами __enter__/__exit__ или функцией‑генератором под @contextmanager. В первом случае мы явно работаем с протоколом, во втором — используем более декларативный синтаксис: всё, что до yield, — настройка, всё, что после, — гарантированная финализация ресурса в блоке finally.
Оцени свой прогресс