将两个上下文管理器合二为一

gue*_*tli 19 python contextmanager

我使用Python 2.7,我知道我可以这样写:

with A() as a, B() as b:
    do_something()
Run Code Online (Sandbox Code Playgroud)

我想提供一个方便的助手,两者都做.此助手的用法应如下所示:

with AB() as ab:
    do_something()
Run Code Online (Sandbox Code Playgroud)

现在AB()应该做两件事:创建上下文A()并创建上下文B().

我不知道如何编写这个便利助手

Mar*_*ers 41

不要重新发明轮子; 这并不像看起来那么简单.

例如,上下文管理器被视为一个堆栈,并且应该以它们输入的相反顺序退出.如果发生异常,则此顺序很重要,因为任何上下文管理器都可以抑制异常,此时其余管理器甚至不会收到通知.该__exit__方法也允许引发不同的异常,然后其他上下文管理器应该能够处理该新异常.接下来,成功创建A()意味着如果B()出现异常,则应通知它.

现在,如果你想要做的就是创建一个你知道的固定数量的上下文管理器,只需在生成器函数上使用@contextlib.contextmanager装饰器:

from contextlib import contextmanager

@contextmanager
def ab_context():
    with A() as a, B() as b:
        yield (a, b)
Run Code Online (Sandbox Code Playgroud)

然后用它作为:

with ab_context() as ab:
Run Code Online (Sandbox Code Playgroud)

如果您需要处理可变数量的上下文管理器,那么不要构建自己的实现; 改为使用标准库contextlib.ExitStack()实现:

from contextlib import ExitStack

with ExitStack() as stack:
    cms = [stack.enter_context(cls()) for cls in (A, B)]

    # ...
Run Code Online (Sandbox Code Playgroud)

ExitStack则负责上下文管理者的正确嵌套,处理退出正确,有序,并用正确的传球异常(包括未通过抑制时的异常,并通过新的立法院引发的异常).

如果您觉得这两行(with和单独的调用enter_context())太繁琐,您可以使用单独的@contextmanager-decorated生成器函数:

from contextlib import ExitStack, contextmanager

@contextmanager
def multi_context(*cms):
    with ExitStack() as stack:
        yield [stack.enter_context(cls()) for cls in cms]
Run Code Online (Sandbox Code Playgroud)

然后使用ab_context这样:

with multi_context(A, B) as ab:
    # ...
Run Code Online (Sandbox Code Playgroud)

对于Python 2,请安装该contextlib2软件包,并使用以下导入:

try:
    from contextlib import ExitStack, contextmanager
except ImportError:
    # Python 2
    from contextlib2 import ExitStack, contextmanager
Run Code Online (Sandbox Code Playgroud)

这可以让你避免在Python 2上重新发明这个轮子.

无论你做什么,都不要使用contextlib.nested(); 出于很好的理由,这已经从Python 3中的库中删除了; 它也没有正确地实现处理嵌套上下文的进入和退出.

  • "这并不像它看起来那么简单" - 这不是真相! (7认同)