Вопрос с собеседования · Python

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

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

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

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

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

Подробно

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__ и проглотить ошибку.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Гарантирует подготовку и очистку вокруг with, даже при исключении
  • ✗__exit__ вызывается, только если блок завершился без исключения, иначе очистку делают вручную
  • ✗with гарантирует только подготовку ресурса, а очистку всё равно нужно вызывать вручную
  • ✗Декоратор contextmanager требует генератор с несколькими yield — до и после блока

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

Разбор темы

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

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования