【问题标题】:Combine two context managers into one将两个上下文管理器合二为一
【发布时间】:2017-08-09 11:44:05
【问题描述】:

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

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

我想提供一个方便的助手,两者兼而有之。这个助手的用法应该是这样的:

with AB() as ab:
    do_something()

现在 AB() 应该同时做:创建上下文 A() 和创建上下文 B()。

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

【问题讨论】:

  • 在您的示例中,您没有使用 abab - 这提出了一个问题:为什么不只使用 with AB():
  • @MartinBonner 是的,你是对的。就我而言,我不需要变量“ab”。 Martin Bonner 的答案使用 (a,b) 很好。

标签: python contextmanager


【解决方案1】:

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

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

现在,如果您只想创建一个固定个预先知道的上下文管理器,只需在生成器函数上使用@contextlib.contextmanager decorator

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() implementation

from contextlib import ExitStack

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

    # ...

ExitStack 然后负责上下文管理器的正确嵌套,按顺序正确处理退出,并正确传递异常(包括在抑制时不传递异常,以及传递新引发的异常)。

如果觉得这两行(with,单独调用enter_context())太繁琐,可以单独使用@contextmanager-decorated generator function

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 package,并使用以下导入:

try:
    from contextlib import ExitStack, contextmanager
except ImportError:
    # Python 2
    from contextlib2 import ExitStack, contextmanager

这也让您避免在 Python 2 上重新发明这个轮子。

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

【讨论】:

  • “这并不像看起来那么简单”——这不是事实!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多