Контекстные менеджеры позволяют Python гарантировать, что ресурс будет очищен, независимо от того, завершится ли код в блоке успешно или с ошибкой. Техническая статья подробно описывает их работу с инструкцией with, а также различные способы их создания и использования, далеко выходящие за рамки управления файлами.
Многие разработчики знакомятся с этим механизмом через with open(path), который автоматически закрывает файл. Однако, по мнению автора, файлы — это наименее интересный случай: инструкция with в первую очередь определяет блок, выход из которого гарантирован. Протокол основывается на двух методах: __enter__(), вызываемом при входе в блок, и __exit__(), вызываемом при выходе, будь то нормальный выход, выход по return или из-за исключения.
Значение, возвращаемое __exit__(), играет решающую роль. Если оно истинно, исключение подавляется, что может остаться незамеченным: программа продолжает работу, а ошибка исчезает бесследно. Если оно False или отсутствует, исключение передаётся дальше после очистки. Именно поэтому менеджеры, представленные в статье, никогда не возвращают истинное значение.
Создание собственных менеджеров
Класс подходит, когда менеджер должен сохранять состояние, которое вызывающая сторона может прочитать. В статье приводится пример таймера, который измеряет длительность выполнения блока с помощью time.perf_counter() и выводит результат, даже если блок завершился с ошибкой. Таким образом, прерванное извлечение признаков выводит «feature extraction: 12.418s (failed)», прежде чем ошибка будет передана дальше.
Для линейной задачи, то есть настройки, выполнения блока и восстановления, декоратор @contextmanager позволяет написать простую генераторную функцию. Всё, что находится до yield, выполняется при входе, всё после — при выходе. Статья рекомендует помещать восстановление в блок finally и никогда не поглощать исключение в try/except без его повторного возбуждения. Через yield должно быть передано только одно значение, иначе возникнет ошибка RuntimeError.
Восстановление состояния и управление несколькими ресурсами
Стандартная библиотека уже предоставляет несколько менеджеров. TemporaryDirectory() создаёт каталог с уникальным именем и удаляет его по завершении блока, что позволяет избежать остаточных файлов от предыдущего запуска. Начиная с Python 3.12, параметр delete=False позволяет сохранять файлы для последующего анализа. В Windows файл, оставшийся открытым, может привести к сбою удаления; параметр ignore_cleanup_errors=True, доступный с Python 3.10, смягчает эту проблему, но не решает её.
Для изменений словарей patch.dict() из модуля unittest.mock восстанавливает исходное состояние после блока: существующий ключ возвращает своё значение, а отсутствующий ключ удаляется. Однако статья подчёркивает, что os.environ является глобальным для процесса. Все задачи и потоки видят это изменение, что делает его рискованным в конкурентном коде.
Когда количество ресурсов известно только во время выполнения, например, количество файлов для слияния, ExitStack группирует менеджеры. Его метод enter_context() регистрирует менеджер, а callback() — простую функцию. При выходе элементы извлекаются из стека в обратном порядке. В статье уточняется, что это извлечение происходит в конце блока или при вызове close(), а не при удалении объекта сборщиком мусора. Специальный класс может быть предпочтительнее, когда стек становится слишком сложным.





