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

28

我使用Python 2.7,我知道我可以编写以下代码:

with A() as a, B() as b:
    do_something()

我希望提供一个方便的帮助器,它可以同时执行两个操作。使用这个帮助器应该像这样:

with AB() as ab:
    do_something()

现在AB()应该做两件事情:创建上下文A()和创建上下文B()。

我不知道如何编写这个便利帮助程序。


1
在你的例子中,你没有使用 abab - 这就引出了一个问题:为什么不只用 with AB(): 呢? - Martin Bonner supports Monica
@MartinBonner 是的,你说得对。在我的情况下,我不需要变量“ab”。Martin Bonner的答案使用了(a,b),这很好。 - guettli
1个回答

64

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

上下文管理器被视为一个堆栈,并且应该按照它们进入的相反顺序退出,例如。如果发生异常,这个顺序很重要,因为任何上下文管理器都可能抑制异常,此时其余的管理器甚至不会被通知到这一点。__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)]

    # ...
< p > 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中已经将其从库中删除,原因非常充分;它也没有正确实现处理嵌套上下文的进入和退出。

9
“this is not as simple as it looks” - “看起来简单的事情实际上并不简单。”- 这话才是真的! - Martin Bonner supports Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接