EMZETT.
Войти

Менеджеры контекста и with: безопасное открытие и закрытие ресурсов

Коротко: Оператор with гарантирует, что ресурсы вроде файлов, соединений или блокировок освобождаются даже при ошибках; собственные менеджеры контекста вы создаёте с помощью классов или contextlib.

Подробнее: Кто открыл файл, должен его закрыть, иначе дескрипторы остаются открытыми, данные незаписанными, блокировки удерживаемыми. try/finally работает, но неудобен. with делает это автоматически.

Подробности

Шаблон

with open("data.txt", encoding="utf-8") as f:
    content = f.read()
# здесь файл гарантированно закрыт, даже при исключении

Эквивалентно try: ... finally: f.close(). Несколько ресурсов: with open(a) as x, open(b, "w") as y:. Типичные менеджеры контекста: файлы, threading.Lock(), соединения/транзакции базы данных (with conn:), tempfile.TemporaryDirectory(), socket, zipfile, unittest.mock.patch, subprocess, HTTP-сеансы (requests.Session).

Собственные менеджеры контекста

Класс с __enter__ и __exit__:

import time
class Stopwatch:
    def __enter__(self):
        self.start = time.perf_counter(); return self
    def __exit__(self, typ, value, tb):
        print(f"{time.perf_counter() - self.start:.3f}s")
        return False        # не глотать исключение

Генератор с contextlib:

from contextlib import contextmanager
@contextmanager
def workdir(path):
    old = os.getcwd(); os.chdir(path)
    try:
        yield
    finally:
        os.chdir(old)

__exit__ получает сведения об исключении; возврат True подавляет его (редко разумно; contextlib.suppress(FileNotFoundError) — безопасный вариант).

Другие инструменты

  • contextlib.closing, ExitStack (динамически много ресурсов), redirect_stdout.
  • async: async with для асинхронных ресурсов (пул базы данных, HTTP-клиент aiohttp), __aenter__/__aexit__.
  • Файлы: Path.read_text() оборачивает with для однострочников.
  • Транзакции: with db.transaction(): автоматически commit/rollback.
  • Тесты: with pytest.raises(ValueError):, with patch(...).
  • Блокировка: with lock: предотвращает забытый release() (см. mutex/deadlock).

Типичные ошибки и советы

  • Оставить файл открытым без with (Windows тогда не сможет его удалить).
  • Возврат из блока with допустим: __exit__ всё равно выполняется.
  • Генераторный менеджер контекста без try/finally не очищает при исключении.
  • Не глотайте исключения: __exit__ возвращает False/None.
  • Всегда указывайте encoding при открытии текста.
  • Утечки ресурсов обнаруживаются через ResourceWarning/-X dev.
  • Связанное: RAII (C++), try-with-resources (Java), defer (Go), using (C#).

См. также: Файлы в Python, Мьютекс