Объедините два контекстных менеджера в один

Я использую 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 по очень веским причинам; он также не реализовал корректную обработку входа и выхода из вложенных контекстов.

Другие вопросы по тегам