将Python上下文管理器的迭代器嵌套在“with”中 [英] Nest an iterator of Python context managers in "with"
问题描述
我有一个返回上下文管理器的迭代器。
I have an iterator that returns context managers.
我想要一个带有语句的pythonic 语句,它模拟了几个嵌套的
带有
语句的行为,迭代器返回的每个上下文管理器都有一个。
I want a pythonic with
statement, that emulates the behaviour of several nested with
statements, one for each context manager returned by the iterator.
可以说,我想要推广(已弃用) contextlib.nested
函数。
One could say, I want a generalisation of the (deprecated) contextlib.nested
function.
推荐答案
contextlib.nested
有两个主要问题导致它被弃用。
contextlib.nested
has two major problems that caused it to be deprecated.
- 第一个问题是内部上下文管理器可能会在
__ init __
或__ new __
期间引发异常,这些异常会导致整个with语句在不调用外部管理器的__ exit __
的情况下中止。 - 第二个问题更复杂。如果其中一个内部经理引发异常而其中一个外部经理通过在
__ exit __
中返回True
来捕获它,块仍应执行。但是在嵌套
的实现中,它只是在不执行块的情况下引发RuntimeError
。此问题可能需要完全重写嵌套
。
- first problem is that inner context managers might raise Exceptions during
__init__
or__new__
, and these exceptions would cause the whole with statement to abort without calling__exit__
of the outer manager. - The second problem is more complicated. If one of the inner managers raises an exception and one of the outer managers catches it by returning
True
in__exit__
, the block should still be executed. But in the implementation ofnested
, it just raises aRuntimeError
without executing the block. This problem probably requires a total rewrite ofnested
.
但这是可能的通过在嵌套
的定义中删除一个 *
来解决第一个问题!
这改变了行为,使得嵌套
不再接受参数列表(这无论如何都没用,因为带有
已经可以处理了)但只有一个迭代器。因此,我将新版本称为 iter_nested
。
然后,用户可以定义迭代器,在迭代期间实例化上下文管理器。
But it is possible to solve the first problem by just removing one *
in the definition of nested
!
This changes the behaviour such that nested
doesn't accept argument lists anymore (which isn't useful anyway because with
can handle that already) but only an iterator. I therefore call the new version "iter_nested
".
The user can then define an iterator that instantiates the context managers during iteration.
生成器的示例:
def contexts():
yield MyContext1()
yield MyContext2()
with iter_nested(contexts()) as contexts:
do_stuff(contexts[0])
do_other_stuff(contexts[1])
原始代码与我更改的嵌套
版本之间的差异在于:
The difference between the codes of the original and my changed version of nested
is here:
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
这篇关于将Python上下文管理器的迭代器嵌套在“with”中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!