PythonJuniorTechnical

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

Context manager — объект с методами __enter__ и __exit__, который описывает захват и освобождение ресурса. with гарантирует вызов __exit__ даже при исключении, что нужно для файлов, lock, транзакций, временных патчей.

Что это и зачем

Context manager — объект, поддерживающий протокол __enter__ / __exit__. Конструкция with cm as x: гарантирует, что cleanup выполнится при выходе из блока: как при нормальном завершении, так и при исключении или break/return. Это решает ту же задачу, что try/finally, но локализует логику ресурса в одном месте.

Протокол

class FileLock:
    def __init__(self, path):
        self.path = path
        self.fp = None

    def __enter__(self):
        self.fp = open(self.path, "w")
        return self.fp          # уходит в переменную после as

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.fp:
            self.fp.close()
        # вернуть True — подавить исключение, обычно возвращаем None (=False)
        return False

with FileLock("data.txt") as f:
    f.write("hello")

Типичные применения

  • Файлы: open().
  • Локи: threading.Lock, asyncio.Lock, multiprocessing.Lock.
  • Транзакции БД: conn.cursor(), session.begin() в SQLAlchemy.
  • Сетевые сессии: requests.Session(), aiohttp.ClientSession().
  • Временные изменения: decimal.localcontext(), unittest.mock.patch().
  • Подавление: contextlib.suppress(FileNotFoundError).
  • Несколько ресурсов: contextlib.ExitStack для динамического набора context manager-ов.

Async-вариант

class AsyncSession:
    async def __aenter__(self):
        self.session = await connect()
        return self.session

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.session.close()

async with AsyncSession() as s:
    await s.fetch("/api")

Подводные камни

  • Открыть ресурс в __init__, а не в __enter__ — если объект создан, но with не выполнен, ресурс утечёт.
  • Возврат True из __exit__ подавляет исключение, и часто это маскирует баги.
  • Многошаговый __enter__ без отката при ошибке в середине — нужен внутренний try/except, который освобождает то, что уже захвачено.
  • Использование with для объекта, у которого нет __enter__/__exit__, даёт AttributeError: __enter__.
  • Смешивание sync (__enter__) и async (__aenter__): asyncio ругается при with на async-ресурсе.

Common mistakes

  • Говорить только про файлы.
  • Не объяснять exit при исключении.
  • Не знать, что context manager может подавить исключение.

What the interviewer is testing

  • Понимает управление ресурсами.
  • Называет enter/exit.
  • Умеет объяснить suppress behavior.

Sources

Related topics