Что такое контекстный менеджер и как работает with?
- Как написать свой контекстный менеджер?
- Что делают __enter__ и __exit__?
- Зачем нужен contextlib.contextmanager?
Контекстный менеджер гарантирует подготовку и очистку вокруг блока with, даже при исключении.
- Протокол:
__enter__при входе,__exit__при выходе - Проще писать через
@contextlib.contextmanagerи генератор с однимyield
А ещё Если __exit__ вернёт True, исключение будет подавлено — обычно так не делают
Подробно
with заменяет связку try/finally для ресурсов: файлов, соединений, блокировок, транзакций, временных настроек.
Как работает with cm as x::
- вызывается
cm.__enter__(), его результат попадает вx; - выполняется тело блока;
- при любом выходе — обычном, через
returnили исключение — вызываетсяcm.__exit__(exc_type, exc, tb). Если исключения не было, все три аргументаNone.
Если __exit__ вернёт истинное значение, исключение подавляется. Так устроен contextlib.suppress, но в своём коде это делают осознанно.
Свой менеджер пишут двумя способами:
- класс с
__enter__и__exit__— когда нужно состояние или повторное использование; - генератор с декоратором
@contextmanager: код доyield— вход, значениеyield— то, что попадёт вas, код после — выход. Очистку ставят вfinally, иначе при исключении она не выполнится.
В одном with можно открыть несколько менеджеров через запятую. Для асинхронного кода есть async with и __aenter__/__aexit__.
Пример
import time
from contextlib import contextmanager
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.3f} с")
with timer("загрузка"):
data = open("big.csv").read()
class Transaction:
def __init__(self, conn):
self.conn = conn
def __enter__(self):
return self.conn.cursor()
def __exit__(self, exc_type, exc, tb):
if exc_type is None:
self.conn.commit()
else:
self.conn.rollback()
return False # исключение не подавляемЧто хотят услышать
- Назначение: гарантированная очистка ресурса.
- Протокол
__enter__/__exit__и что получает__exit__. - Способ через
contextmanagerсtry/finallyвокругyield. - Примеры: файлы, блокировки, транзакции.
Частые ошибки
- В генераторном менеджере не обернуть
yieldвtry/finally. - Случайно вернуть True из
__exit__и проглотить ошибку.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Гарантирует подготовку и очистку вокруг with, даже при исключении
- ✗__exit__ вызывается, только если блок завершился без исключения, иначе очистку делают вручную
- ✗with гарантирует только подготовку ресурса, а очистку всё равно нужно вызывать вручную
- ✗Декоратор contextmanager требует генератор с несколькими yield — до и после блока
Что спросят дальше
Разбор темы
- Исключения и контекстные менеджеры
try, except, else и finally, свои исключения и цепочки raise from, with и контекстные менеджеры, ExceptionGroup.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.