Менеджеры контекста в Python
Гарантируйте, что подготовка всегда сопровождается освобождением, даже когда что-то идёт не так
with: __enter__ выполняется на входе, а __exit__ гарантированно выполняется на выходе — неважно, завершился ли блок нормально, встретил return или выбросил исключение. Это ответ языка на задачу "захватить ресурс, использовать его и обязательно освободить".
- объяснить, как
withотображается на протокол__enter__/__exit__, - написать надёжный менеджер контекста на основе класса, включая корректную обработку исключений в
__exit__, - создать его гораздо дешевле с помощью
@contextlib.contextmanagerи генератора, - рассуждать о возвращаемом значении
__exit__и о том, когда оно поглощает исключения, - динамически объединять несколько менеджеров через
contextlib.ExitStack, - использовать
suppress,closingиnullcontextвместо шаблонного кода.
1. Проблема: очистка, которую нельзя пропустить
Почти любая нетривиальная программа захватывает ресурсы, которые затем нужно освободить: открытые файлы, сетевые сокеты, соединения с базой данных, блокировки потоков, временные каталоги. Наивная версия допускает утечку, как только что-либо выбрасывает исключение:
f = open("data.txt")
process(f.read()) # если здесь исключение, следующая строка не выполнится
f.close() # УТЕЧКА: файл никогда не закрывается
Исторически правильное решение — try/finally: блок finally выполняется независимо от того, как завершился блок try.
f = open("data.txt")
try:
process(f.read())
finally:
f.close() # ВЫПОЛНЯЕТСЯ ВСЕГДА, даже при исключении
Это работает, но многословно, легко забывается, а захват и освобождение расходятся по мере роста блока. Менеджер контекста упаковывает ровно эту дисциплину try/finally в переиспользуемый объект, так что вызывающий код пишет одну чистую строку.