ENIGMA AI
ENIGMA AI

Что такое контекстный менеджер в Python?

встречается 7× Python junior language_specific

Как ответить

Контекстный менеджер в Python — это объект, который определяет методы __enter__ и __exit__, и используется с конструкцией with. Его главная задача — гарантировать выполнение кода при входе в блок и при выходе из него, даже если внутри произошла ошибка. Самый частый пример — работа с файлами: открыл, прочитал, закрыл. Без контекстного менеджера легко забыть вызвать close(), особенно если код упал. С with это происходит автоматически.

Вот как это выглядит на практике. Без контекстного менеджера:

f = open('file.txt', 'r')
try:
    data = f.read()
finally:
    f.close()

С контекстным менеджером:

with open('file.txt', 'r') as f:
    data = f.read()

Код короче, и блок finally не нужен — __exit__ вызовется в любом случае. Встроенные менеджеры есть не только для файлов: threading.Lock для блокировок, subprocess.Popen для процессов, contextlib.redirect_stdout для перенаправления вывода. Можно написать и свой. Допустим, нужно замерить время выполнения функции:

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.elapsed = time.time() - self.start
        print(f'Elapsed: {self.elapsed:.2f}s')

with Timer() as t:
    # какой-то код
    time.sleep(1)
print(t.elapsed)  # 1.00

Обрати внимание на сигнатуру __exit__: три параметра — тип исключения, значение и traceback. Если внутри блока произошла ошибка, они передаются в этот метод. Если метод возвращает True, исключение подавляется. Если False или None — пробрасывается дальше. Это удобно, например, для логирования ошибок без остановки выполнения.

Ещё есть contextlib.contextmanager — декоратор, который превращает генератор в контекстный менеджер. Тот же таймер можно написать так:

from contextlib import contextmanager

@contextmanager
def timer():
    start = time.time()
    try:
        yield
    finally:
        print(f'Elapsed: {time.time() - start:.2f}s')

with timer():
    time.sleep(1)

Тут важно: yield разделяет код до и после блока. Всё, что до yield, — это __enter__, всё, что после, — __exit__. Оборачивать в try/finally нужно, чтобы гарантировать выполнение кода даже при исключении.

На собеседовании я бы ещё упомянул, что контекстные менеджеры часто используют для управления соединениями с БД (открыть/закрыть), транзакциями (commit/rollback) или временными файлами. Это стандартный паттерн для ресурсов, которые нужно освобождать.

Ключевые тезисы

  • Контекстный менеджер реализует методы __enter__ и __exit__, используется с with.
  • Гарантирует выполнение кода при выходе из блока, включая обработку исключений.
  • Встроенные примеры: open(), threading.Lock, contextlib.redirect_stdout.
  • Можно написать свой через класс или декоратор @contextmanager.
  • __exit__ принимает три параметра (exc_type, exc_val, exc_tb) и может подавлять исключения.

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

  • — Как работает @contextmanager под капотом? Почему yield обязательно оборачивать в try/finally?
  • — Что будет, если в __exit__ вернуть True? Приведи пример, когда это полезно.
  • — Как реализовать контекстный менеджер для работы с транзакцией в SQLite, чтобы при ошибке делался rollback?

Готовьтесь к собеседованию с ENIGMA AI

AI-суфлёр подсказывает ответы прямо на собеседовании в реальном времени — незаметно для интервьюера.

Скачать приложение