Короткий ответ
ExitStack — контекстный менеджер, который динамически накапливает произвольное число других контекстных менеджеров через enter_context и гарантированно закрывает их все в обратном порядке при выходе, что удобно, когда состав ресурсов определяется во время выполнения.
Как это работает подробнее
Вместо вложенных with a, with b, with c... ExitStack позволяет в цикле или по условию вызывать stack.enter_context(cm) для произвольного количества менеджеров, и все они будут закрыты корректно при выходе из блока with ExitStack(), в том числе при исключении.
Пример кода
example-277.py
from contextlib import ExitStack
def process_files(paths):
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
# все файлы в paths открыты здесь одновременно
for f in files:
print(f.readline())
# при выходе из блока все файлы гарантированно закрыты, даже если paths было динамическимЧто использовать на практике
- Практика. Полезен, когда список файлов/соединений для открытия определяется динамически (по конфигурации или входным данным);
pop_all()позволяет передать владение ресурсами другому объекту без их закрытия в текущем месте.
Подводные камни
- Риски. Пытаются эмулировать динамическое число ресурсов через вложенные
withс дублированием кода; забывают, что закрытие происходит в обратном порядке добавления, и выстраивают неверные ожидания относительно порядка освобождения зависимых ресурсов.