Получить доступ

Что такое контекстный менеджер и как работает with?

JuniorИсключения и контекстные менеджеры
Ещё спрашивают так:
  • Как написать свой контекстный менеджер?
  • Что делают __enter__ и __exit__?
  • Зачем нужен contextlib.contextmanager?
Коротко

Контекстный менеджер гарантирует подготовку и очистку вокруг блока with, даже при исключении.

  • Протокол: __enter__ при входе, __exit__ при выходе
  • Проще писать через @contextlib.contextmanager и генератор с одним yield

А ещё Если __exit__ вернёт True, исключение будет подавлено — обычно так не делают

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

with заменяет связку try/finally для ресурсов: файлов, соединений, блокировок, транзакций, временных настроек.

Как работает with cm as x::

  1. вызывается cm.__enter__(), его результат попадает в x;
  2. выполняется тело блока;
  3. при любом выходе — обычном, через 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__ и проглотить ошибку.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем