将 Python 上下文管理器的迭代器嵌套在“with”中

Tur*_*ion 5 python iterator nested contextmanager

我有一个返回上下文管理器的迭代器。

我想要一个 pythonicwith语句,它模拟多个嵌套语句的行为with,每个嵌套语句对应迭代器返回的每个上下文管理器。

有人可能会说,我想要(已弃用的)函数的泛化contextlib.nested

Tur*_*ion 1

contextlib.nested有两个主要问题导致其被弃用。

  1. 第一个问题是内部上下文管理器可能会在__init__or期间引发异常__new__,并且这些异常将导致整个 with 语句中止而不调用__exit__外部管理器。
  2. 第二个问题更复杂。如果内部管理器之一引发异常,并且外部管理器之一通过返回Truein捕获该异常__exit__,则仍应执行该块。但在 的实现中nested,它只是引发了 aRuntimeError而不执行该块。这个问题可能需要完全重写nested.

但只需删除!定义中的一个就可以解决第一个问题。这改变了行为,不再接受参数列表(无论如何都没有用,因为已经可以处理它),而只接受迭代器。因此我将新版本称为“*nestednestedwithiter_nested ”。然后,用户可以定义一个迭代器,在迭代期间实例化上下文管理器。

带有生成器的示例:

def contexts():
    yield MyContext1()
    yield MyContext2()

with iter_nested(contexts()) as contexts:
    do_stuff(contexts[0])
    do_other_stuff(contexts[1])
Run Code Online (Sandbox Code Playgroud)

原来的代码和我修改后的版本的代码之间的区别nested如下:

from contextlib import contextmanager

@contextmanager
--- def nested(*managers):
+++ def iter_nested(mgr_iterator):
    --- #comments & deprecation warning
    exits = []
    vars = []
    --- exc = (None, None, None)
    +++ exc = None # Python 3
    try:
        --- for mgr in managers:
        +++ for mgr in mgr_iterator:
            exit = mgr.__exit__
            enter = mgr.__enter__
            vars.append(enter())
            exits.append(exit)
        yield vars
# All of the following is new and fit for Python 3
except Exception as exception:
    exc = exception
    exc_tuple = (type(exc), exc, exc.__traceback__)
else:
    exc_tuple = (None, None, None)
finally:
    while exits:
        exit = exits.pop()
        try:
            if exit(*exc_tuple):
                exc = None
                exc_tuple = (None, None, None)
        except Exception as exception:
            exception.__context__ = exc
            exc = exception
            exc_tuple = (type(exc), exc, exc.__traceback__)
    if exc:
        raise exc
Run Code Online (Sandbox Code Playgroud)