3.4.7 动态上下文管理器栈
大多数上下文管理器都一次处理一个对象,如单个文件或数据库句柄。在这些情况下,对象是提前已知的,并且使用上下文管理器的代码可以建立一个对象上。另外一些情况下,程序可能需要在一个上下文中创建未知数目的对象,控制流退出这个上下文时所有这些对象都要清理。ExitStack就是用来处理这些更动态的情况。
ExitStack实例会维护清理回调的一个栈数据结构。这些回调显式地填充在上下文中,在控制流退出上下文时会以逆序调用所有注册的回调。结果类似于有多个嵌套的with语句,只不过它们是动态建立的。
3.4.7.1 上下文管理器入栈
可以使用多种方法填充ExitStack。下面这个例子使用enter_context()来为栈增加一个新的上下文管理器。
import contextlib
@contextlib.contextmanager
def make_context(i):
print('{} entering'.format(i))
yield {}
print('{} exiting'.format(i))
def variable_stack(n,msg):
with contextlib.ExitStack() as stack:
for i in range(n):
stack.enter_context(make_context(i))
print(msg)
variable_stack(2,'inside context')
enter_context()首先在上下文管理器上调用__enter__()。然后把它的__exit__()方法注册为一个回调,撤销栈时将调用这个回调。
提供给ExitStack的上下文管理器被当作出现在一系列嵌套的with语句中。这个上下文发生的错误会通过上下文管理器正常的错误处理来传播。下面的上下文管理器类展示了错误如何传播。
# contextlib_context_managers.py
import contextlib
class Tracker:
"Base class for noisy context managers."
def __init__(self,i):
self.i = i
def msg(self,s):
print(' {}({}):{}'.format(
self.__class__.__name__,self.i,s))
def __enter__(self):
self.msg('entering')
class HandleError(Tracker):
"If an exception is received,treat it as handled."
def __exit__(self,*exc_details):
received_exc = exc_details[1] is not None
if received_exc:
self.msg('handling exceotion {!r}'.format(
exc_details[1]
))
self.msg('exiting {}'.format(received_exc))
# Return a boolean value indicating whether the exception was handled.
return received_exc
class PassError(Tracker):
"If an exception is received,propagate it."
def __exit__(self,*exc_details):
received_exc = exc_details[1] is not None
if received_exc:
self.msg('passing exception {!r}'.format(exc_details[1]))
self.msg('exiting')
# Return False,indicating any exception was not handled.
return False
class ErrorOnExit(Tracker):
"Cause an exception"
def __exit__(self,*exc_details):
self.msg('throwing error')
raise RuntimeError('from {}'.format(self.i))
class ErrorOnEnter(Tracker):
"Cause an exception"
def __enter__(self):
self.msg('throwing error on enter')
raise RuntimeError('from {}'.format(self.i))
def __exit__(self,*exc_info):
self.msg('exiting')
下面的例子使用了这些类,它基于variable_stack(),这个函数使用传入的上下文管理器来构造一个ExitStack,继而逐步地构建整个上下文。这些例子传入了不同的上下文管理器来研究错误处理行为。第一个例子展示了没有异常的正常情况。
print('No errors:')
variable_stack([HandleError(1),
PassError(2),
])
下一个例子展示了栈末尾的上下文管理器中的错误处理,撤销栈时,所有打开的上下文都会关闭。
print('\nError at the end of the context stack:')
variable_stack([
HandleError(1),
HandleError(2),
ErrorOnExit(3),
])
下一个例子中,异常会在栈中间的上下文管理器内被处理。直到一些上下文已经关闭时这些错误才出现,所以那些上下文不会看到这个错误。
print('\nError in the middle of the context stack:')
variable_stack([
HandleError(1),
PassError(2),
ErrorOnExit(3),
HandleError(4),
])
最后一个例子展示了异常处理并且继续向上传播到其调用代码的情况。
try:
print('\nError ignored:')
variable_stack([
PassError(1),
ErrorOnExit(2),
])
except RuntimeError:
print('error handled outside of context')
如果栈中的任何上下文管理器接收到一个异常并返回一个True值,那么这会阻止该异常继续向上传播到任何其他上下文管理器。
运行结果: