Менеджеры контекста и 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, Мьютекс