<p>在不希望<code>with</code>语句在所有资源获取成功时清理内容的情况下操作上下文管理器是<a href="https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack" rel="nofollow noreferrer">^{<cd2>}</a>设计用来处理的用例之一。</p>
<p>例如(使用<code>addCleanup()</code>而不是自定义的<code>tearDown()</code>实现):</p>
<pre><code>def setUp(self):
with contextlib.ExitStack() as stack:
self._resource = stack.enter_context(GetResource())
self.addCleanup(stack.pop_all().close)
</code></pre>
<p>这是最稳健的方法,因为它能正确处理多个资源的获取:</p>
<pre><code>def setUp(self):
with contextlib.ExitStack() as stack:
self._resource1 = stack.enter_context(GetResource())
self._resource2 = stack.enter_context(GetOtherResource())
self.addCleanup(stack.pop_all().close)
</code></pre>
<p>在这里,如果<code>GetOtherResource()</code>失败,第一个资源将立即被with语句清理,而如果成功,则<code>pop_all()</code>调用将推迟清理,直到注册的清理函数运行为止。</p>
<p>如果您知道您只需要管理一个资源,可以跳过with语句:</p>
<pre><code>def setUp(self):
stack = contextlib.ExitStack()
self._resource = stack.enter_context(GetResource())
self.addCleanup(stack.close)
</code></pre>
<p>但是,这有点容易出错,因为如果在没有首先切换到基于with语句的版本的情况下向堆栈中添加更多资源,那么如果以后的资源获取失败,成功分配的资源可能不会得到及时清理。</p>
<p>通过保存对测试用例上资源堆栈的引用,还可以使用自定义<code>tearDown()</code>实现编写类似的东西:</p>
<pre><code>def setUp(self):
with contextlib.ExitStack() as stack:
self._resource1 = stack.enter_context(GetResource())
self._resource2 = stack.enter_context(GetOtherResource())
self._resource_stack = stack.pop_all()
def tearDown(self):
self._resource_stack.close()
</code></pre>
<p>或者,您还可以定义一个自定义清理函数,该函数通过闭包引用访问资源,从而避免在测试用例上存储任何额外的状态,而这些状态纯粹是为了清理:</p>
<pre><code>def setUp(self):
with contextlib.ExitStack() as stack:
resource = stack.enter_context(GetResource())
def cleanup():
if necessary:
one_last_chance_to_use(resource)
stack.pop_all().close()
self.addCleanup(cleanup)
</code></pre>