Объедините два контекстных менеджера в один
Я использую Python 2.7, и я знаю, что я могу написать это:
with A() as a, B() as b:
do_something()
Я хочу обеспечить вспомогательный помощник, который делает оба. Использование этого помощника должно выглядеть так:
with AB() as ab:
do_something()
Теперь AB() должен сделать оба: создать контекст A() и создать контекст B().
Понятия не имею, как написать этот вспомогательный инструмент
1 ответ
Не изобретай велосипед заново; это не так просто, как кажется.
Менеджеры контекста обрабатываются как стек и должны быть закрыты, например, в обратном порядке. Если произошло исключение, этот порядок имеет значение, так как любой менеджер контекста может подавить исключение, и в этот момент остальные менеджеры даже не получат уведомление об этом. __exit__
Метод также может вызывать другое исключение, и тогда другие менеджеры контекста должны иметь возможность обрабатывать это новое исключение. Далее успешно создаем A()
означает, что следует уведомить, если B()
не удалось с исключением.
Теперь, если все, что вы хотите сделать, это создать фиксированное количество менеджеров контекста, которые вы знаете заранее, просто используйте @contextlib.contextmanager
декоратор на функции генератора:
from contextlib import contextmanager
@contextmanager
def ab_context():
with A() as a, B() as b:
yield (a, b)
тогда используйте это как:
with ab_context() as ab:
Если вам нужно обрабатывать переменное количество менеджеров контекста, не создавайте свою собственную реализацию; использовать стандартную библиотеку contextlib.ExitStack()
реализация вместо:
from contextlib import ExitStack
with ExitStack() as stack:
cms = [stack.enter_context(cls()) for cls in (A, B)]
# ...
ExitStack
затем заботится о правильном вложении менеджеров контекста, обрабатывая выход правильно, по порядку и с правильной передачей исключений (в том числе не передавая исключение при подавлении и передавая вновь возникшие исключения).
Если вы чувствуете две линии (with
и отдельные звонки enter_context()
) слишком утомительно, вы можете использовать отдельный @contextmanager
функция генератора:
from contextlib import ExitStack, contextmanager
@contextmanager
def multi_context(*cms):
with ExitStack() as stack:
yield [stack.enter_context(cls()) for cls in cms]
затем используйте ab_context
как это:
with multi_context(A, B) as ab:
# ...
Для Python 2 установите contextlib2
пакет, и используйте следующий импорт:
try:
from contextlib import ExitStack, contextmanager
except ImportError:
# Python 2
from contextlib2 import ExitStack, contextmanager
Это позволяет избежать повторного изобретения этого колеса на Python 2.
Что бы вы ни делали, не используйте contextlib.nested()
; это было удалено из библиотеки в Python 3 по очень веским причинам; он также не реализовал корректную обработку входа и выхода из вложенных контекстов.