uTools-Manuals/docs/python/contextlib.html
2019-04-21 11:50:48 +08:00

292 lines
74 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="body" role="main"><div class="section" id="module-contextlib"><h1><span class="yiyi-st" id="yiyi-10">29.6. <a class="reference internal" href="#module-contextlib" title="contextlib: Utilities for with-statement contexts."><code class="xref py py-mod docutils literal"><span class="pre">contextlib</span></code></a><a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的上下文实用程序</span></h1><p><span class="yiyi-st" id="yiyi-11"><strong>源代码:</strong> <a class="reference external" href="https://hg.python.org/cpython/file/3.5/Lib/contextlib.py">Lib/contextlib.py</a></span></p><p><span class="yiyi-st" id="yiyi-12">此模块为涉及<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的常见任务提供实用程序。</span><span class="yiyi-st" id="yiyi-13">有关详细信息,请参阅<a class="reference internal" href="stdtypes.html#typecontextmanager"><span>Context Manager Types</span></a><a class="reference internal" href="../reference/datamodel.html#context-managers"><span>With Statement Context Managers</span></a></span></p><div class="section" id="utilities"><h2><span class="yiyi-st" id="yiyi-14">29.6.1. </span><span class="yiyi-st" id="yiyi-15">实用程序</span></h2><p><span class="yiyi-st" id="yiyi-16">提供的函数和类:</span></p><dl class="function"><dt id="contextlib.contextmanager"><span class="yiyi-st" id="yiyi-17"> <code class="descclassname">@</code><code class="descclassname">contextlib.</code><code class="descname">contextmanager</code></span></dt><dd><p><span class="yiyi-st" id="yiyi-18">这个函数是一个<a class="reference internal" href="../glossary.html#term-decorator"><span class="xref std std-term">装饰器</span></a>,它可以用来一个工厂函数用于<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句上下文管理器,而不需要创建一个类或单独的<a class="reference internal" href="../reference/datamodel.html#object.__enter__" title="object.__enter__"><code class="xref py py-meth docutils literal"><span class="pre">__enter__()</span></code></a><a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法。</span></p><p><span class="yiyi-st" id="yiyi-19">一个简单的例子这不推荐作为一个真正的方式生成HTML</span><span class="yiyi-st" id="yiyi-20"></span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">contextmanager</span>
<span class="nd">@contextmanager</span>
<span class="k">def</span> <span class="nf">tag</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"&lt;</span><span class="si">%s</span><span class="s2">&gt;"</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
<span class="k">yield</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"&lt;/</span><span class="si">%s</span><span class="s2">&gt;"</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">with</span> <span class="n">tag</span><span class="p">(</span><span class="s2">"h1"</span><span class="p">):</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"foo"</span><span class="p">)</span>
<span class="o">...</span>
<span class="o">&lt;</span><span class="n">h1</span><span class="o">&gt;</span>
<span class="n">foo</span>
<span class="o">&lt;/</span><span class="n">h1</span><span class="o">&gt;</span>
</code></pre><p><span class="yiyi-st" id="yiyi-21">被装饰的函数在调用时必须返回一个<a class="reference internal" href="../glossary.html#term-generator"><span class="xref std std-term">生成器</span></a>迭代器。</span><span class="yiyi-st" id="yiyi-22">这个迭代器必须只产生一个值,它将绑定到<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的<a class="reference internal" href="../reference/compound_stmts.html#as"><code class="xref std std-keyword docutils literal"><span class="pre">as</span></code></a>子句的目标(如果有)。</span></p><p><span class="yiyi-st" id="yiyi-23">在生成器yield的点执行嵌套<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>在语句中的代码块。</span><span class="yiyi-st" id="yiyi-24">生成器然后在这个代码块退出之后恢复执行。</span><span class="yiyi-st" id="yiyi-25">如果在这个代码块中发生未处理的异常则在生成器内部出现yield的点重新生成这个异常。</span><span class="yiyi-st" id="yiyi-26">因此,你可以使用<a class="reference internal" href="../reference/compound_stmts.html#try"><code class="xref std std-keyword docutils literal"><span class="pre">try</span></code></a>...<a class="reference internal" href="../reference/compound_stmts.html#except"><code class="xref std std-keyword docutils literal"><span class="pre">except</span></code></a>...<a class="reference internal" href="../reference/compound_stmts.html#finally"><code class="xref std std-keyword docutils literal"><span class="pre">finally</span></code></a>语句来捕获错误,或确保做出某些清理。</span><span class="yiyi-st" id="yiyi-27">如果一个异常被捕获只是为了记录它或执行一些操作(而不是完全抑制它),生成器必须重新引发该异常。</span><span class="yiyi-st" id="yiyi-28">否则,生成器上下文管理器将告诉<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句异常已经处理,执行将紧随<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句之后的语句重新开始。</span></p><p><span class="yiyi-st" id="yiyi-29"><a class="reference internal" href="#contextlib.contextmanager" title="contextlib.contextmanager"><code class="xref py py-func docutils literal"><span class="pre">contextmanager()</span></code></a>使用<a class="reference internal" href="#contextlib.ContextDecorator" title="contextlib.ContextDecorator"><code class="xref py py-class docutils literal"><span class="pre">ContextDecorator</span></code></a>,因此它创建的上下文管理器可以用作装饰器以及<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句。</span><span class="yiyi-st" id="yiyi-30">当用作装饰器时,在每个函数调用上隐含地创建新的生成器实例(这允许由<a class="reference internal" href="#contextlib.contextmanager" title="contextlib.contextmanager"><code class="xref py py-func docutils literal"><span class="pre">contextmanager()</span></code></a>创建的否则“一次性”上下文管理器满足上下文管理器支持多个调用以便用作装饰器)。</span></p><div class="versionchanged"><p><span class="yiyi-st" id="yiyi-31"><span class="versionmodified">在版本3.2中更改:</span>使用<a class="reference internal" href="#contextlib.ContextDecorator" title="contextlib.ContextDecorator"><code class="xref py py-class docutils literal"><span class="pre">ContextDecorator</span></code></a></span></p></div></dd></dl><dl class="function"><dt id="contextlib.closing"><span class="yiyi-st" id="yiyi-32"> <code class="descclassname">contextlib.</code><code class="descname">closing</code><span class="sig-paren">(</span><em>thing</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-33">返回一个上下文管理器,在块完成后关闭<em></em></span><span class="yiyi-st" id="yiyi-34">这基本上等同于:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">contextmanager</span>
<span class="nd">@contextmanager</span>
<span class="k">def</span> <span class="nf">closing</span><span class="p">(</span><span class="n">thing</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">thing</span>
<span class="k">finally</span><span class="p">:</span>
<span class="n">thing</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-35">并让你编写如下代码:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">closing</span>
<span class="kn">from</span> <span class="nn">urllib.request</span> <span class="k">import</span> <span class="n">urlopen</span>
<span class="k">with</span> <span class="n">closing</span><span class="p">(</span><span class="n">urlopen</span><span class="p">(</span><span class="s1">'http://www.python.org'</span><span class="p">))</span> <span class="k">as</span> <span class="n">page</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">page</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre><p><span class="yiyi-st" id="yiyi-36">而无需显式关闭<code class="docutils literal"><span class="pre">page</span></code></span><span class="yiyi-st" id="yiyi-37">即使出现错误,当退出<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>块时,将调用<code class="docutils literal"><span class="pre">page.close()</span></code></span></p></dd></dl><dl class="function"><dt id="contextlib.suppress"><span class="yiyi-st" id="yiyi-38"> <code class="descclassname">contextlib.</code><code class="descname">suppress</code><span class="sig-paren">(</span><em>*exceptions</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-39">返回上下文管理器如果它们出现在with语句的主体中则禁止任何指定的异常然后使用with语句结束后的第一个语句恢复执行。</span></p><p><span class="yiyi-st" id="yiyi-40">与任何其他完全抑制异常的机制一样,这个上下文管理器应当仅用于覆盖非常具体的错误,其中静默地继续执行程序是已知的正确的事情。</span></p><p><span class="yiyi-st" id="yiyi-41">例如:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">suppress</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">FileNotFoundError</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="s1">'somefile.tmp'</span><span class="p">)</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">FileNotFoundError</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="s1">'someotherfile.tmp'</span><span class="p">)</span>
</code></pre><p><span class="yiyi-st" id="yiyi-42">此代码等效于:</span></p><pre><code class="language-python"><span></span><span class="k">try</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="s1">'somefile.tmp'</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="s1">'someotherfile.tmp'</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
<span class="k">pass</span>
</code></pre><p><span class="yiyi-st" id="yiyi-43">此上下文管理器为<a class="reference internal" href="#reentrant-cms"><span>reentrant</span></a></span></p><div class="versionadded"><p><span class="yiyi-st" id="yiyi-44"><span class="versionmodified">版本3.4中的新功能。</span></span></p></div></dd></dl><dl class="function"><dt id="contextlib.redirect_stdout"><span class="yiyi-st" id="yiyi-45"> <code class="descclassname">contextlib.</code><code class="descname">redirect_stdout</code><span class="sig-paren">(</span><em>new_target</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-46">上下文管理器用于将<a class="reference internal" href="sys.html#sys.stdout" title="sys.stdout"><code class="xref py py-data docutils literal"><span class="pre">sys.stdout</span></code></a>临时重定向到另一个文件或类文件对象。</span></p><p><span class="yiyi-st" id="yiyi-47">这个工具为现有的输出硬连接到stdout的函数或类增加了灵活性。</span></p><p><span class="yiyi-st" id="yiyi-48">例如,<a class="reference internal" href="functions.html#help" title="help"><code class="xref py py-func docutils literal"><span class="pre">help()</span></code></a>的输出通常发送到<em>sys.stdout</em></span><span class="yiyi-st" id="yiyi-49">通过将输出重定向到<a class="reference internal" href="io.html#io.StringIO" title="io.StringIO"><code class="xref py py-class docutils literal"><span class="pre">io.StringIO</span></code></a>对象,您可以在字符串中捕获该输出:</span></p><pre><code class="language-python"><span></span><span class="n">f</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">StringIO</span><span class="p">()</span>
<span class="k">with</span> <span class="n">redirect_stdout</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
<span class="n">help</span><span class="p">(</span><span class="nb">pow</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">getvalue</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-50">要将<a class="reference internal" href="functions.html#help" title="help"><code class="xref py py-func docutils literal"><span class="pre">help()</span></code></a>的输出发送到磁盘上的文件,请将输出重定向到常规文件:</span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'help.txt'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">with</span> <span class="n">redirect_stdout</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
<span class="n">help</span><span class="p">(</span><span class="nb">pow</span><span class="p">)</span>
</code></pre><p><span class="yiyi-st" id="yiyi-51"><a class="reference internal" href="functions.html#help" title="help"><code class="xref py py-func docutils literal"><span class="pre">help()</span></code></a>的输出发送到<em>sys.stderr</em></span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="n">redirect_stdout</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">):</span>
<span class="n">help</span><span class="p">(</span><span class="nb">pow</span><span class="p">)</span>
</code></pre><p><span class="yiyi-st" id="yiyi-52">请注意,对<a class="reference internal" href="sys.html#sys.stdout" title="sys.stdout"><code class="xref py py-data docutils literal"><span class="pre">sys.stdout</span></code></a>的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。</span><span class="yiyi-st" id="yiyi-53">它也对子进程的输出没有影响。</span><span class="yiyi-st" id="yiyi-54">然而,它对许多实用程序脚本仍然是一个有用的方法。</span></p><p><span class="yiyi-st" id="yiyi-55">此上下文管理器为<a class="reference internal" href="#reentrant-cms"><span>reentrant</span></a></span></p><div class="versionadded"><p><span class="yiyi-st" id="yiyi-56"><span class="versionmodified">版本3.4中的新功能。</span></span></p></div></dd></dl><dl class="function"><dt id="contextlib.redirect_stderr"><span class="yiyi-st" id="yiyi-57"> <code class="descclassname">contextlib.</code><code class="descname">redirect_stderr</code><span class="sig-paren">(</span><em>new_target</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-58"><a class="reference internal" href="#contextlib.redirect_stdout" title="contextlib.redirect_stdout"><code class="xref py py-func docutils literal"><span class="pre">redirect_stdout()</span></code></a>类似,但将<a class="reference internal" href="sys.html#sys.stderr" title="sys.stderr"><code class="xref py py-data docutils literal"><span class="pre">sys.stderr</span></code></a>重定向到另一个文件或类文件对象。</span></p><p><span class="yiyi-st" id="yiyi-59">此上下文管理器为<a class="reference internal" href="#reentrant-cms"><span>reentrant</span></a></span></p><div class="versionadded"><p><span class="yiyi-st" id="yiyi-60"><span class="versionmodified">版本3.5中的新功能。</span></span></p></div></dd></dl><dl class="class"><dt id="contextlib.ContextDecorator"><span class="yiyi-st" id="yiyi-61"> <em class="property">class </em><code class="descclassname">contextlib.</code><code class="descname">ContextDecorator</code></span></dt><dd><p><span class="yiyi-st" id="yiyi-62">允许上下文管理器也用作装饰器的基类。</span></p><p><span class="yiyi-st" id="yiyi-63">继承自<code class="docutils literal"><span class="pre">ContextDecorator</span></code>的上下文管理器必须正常实现<code class="docutils literal"><span class="pre">__enter__</span></code><code class="docutils literal"><span class="pre">__exit__</span></code></span><span class="yiyi-st" id="yiyi-64"><code class="docutils literal"><span class="pre">__exit__</span></code>即使在用作装饰器时仍保留其可选的异常处理。</span></p><p><span class="yiyi-st" id="yiyi-65"><code class="docutils literal"><span class="pre">ContextDecorator</span></code><a class="reference internal" href="#contextlib.contextmanager" title="contextlib.contextmanager"><code class="xref py py-func docutils literal"><span class="pre">contextmanager()</span></code></a>使用,因此您可以自动获得此功能。</span></p><p><span class="yiyi-st" id="yiyi-66"><code class="docutils literal"><span class="pre">ContextDecorator</span></code>的示例:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ContextDecorator</span>
<span class="k">class</span> <span class="nc">mycontext</span><span class="p">(</span><span class="n">ContextDecorator</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Starting'</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">exc</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Finishing'</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="o">&gt;&gt;&gt;</span> <span class="nd">@mycontext</span><span class="p">()</span>
<span class="o">...</span> <span class="k">def</span> <span class="nf">function</span><span class="p">():</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="s1">'The bit in the middle'</span><span class="p">)</span>
<span class="o">...</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">function</span><span class="p">()</span>
<span class="n">Starting</span>
<span class="n">The</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">the</span> <span class="n">middle</span>
<span class="n">Finishing</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">with</span> <span class="n">mycontext</span><span class="p">():</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="s1">'The bit in the middle'</span><span class="p">)</span>
<span class="o">...</span>
<span class="n">Starting</span>
<span class="n">The</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">the</span> <span class="n">middle</span>
<span class="n">Finishing</span>
</code></pre><p><span class="yiyi-st" id="yiyi-67">这种改变只是以下形式的任何构造的语法糖:</span></p><pre><code class="language-python"><span></span><span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
<span class="k">with</span> <span class="n">cm</span><span class="p">():</span>
<span class="c1"># Do stuff</span>
</code></pre><p><span class="yiyi-st" id="yiyi-68"><code class="docutils literal"><span class="pre">ContextDecorator</span></code>可让您改为:</span></p><pre><code class="language-python"><span></span><span class="nd">@cm</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
<span class="c1"># Do stuff</span>
</code></pre><p><span class="yiyi-st" id="yiyi-69">它清楚地表明,<code class="docutils literal"><span class="pre">cm</span></code>适用于整个函数,而不仅仅是它的一部分(并且保存缩进级别也很好)。</span></p><p><span class="yiyi-st" id="yiyi-70">已经具有基类的现有上下文管理器可以通过使用<code class="docutils literal"><span class="pre">ContextDecorator</span></code>作为mixin类来扩展</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ContextDecorator</span>
<span class="k">class</span> <span class="nc">mycontext</span><span class="p">(</span><span class="n">ContextBaseClass</span><span class="p">,</span> <span class="n">ContextDecorator</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">exc</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">False</span>
</code></pre><div class="admonition note"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-71">注意</span></p><p class="last"><span class="yiyi-st" id="yiyi-72">由于装饰函数必须能够被多次调用,底层上下文管理器必须支持在多个<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句中使用。</span><span class="yiyi-st" id="yiyi-73">如果不是这种情况,那么应该使用在函数中具有显式<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的原始结构。</span></p></div><div class="versionadded"><p><span class="yiyi-st" id="yiyi-74"><span class="versionmodified">版本3.2中的新功能。</span></span></p></div></dd></dl><dl class="class"><dt id="contextlib.ExitStack"><span class="yiyi-st" id="yiyi-75"> <em class="property">class </em><code class="descclassname">contextlib.</code><code class="descname">ExitStack</code></span></dt><dd><p><span class="yiyi-st" id="yiyi-76">上下文管理器被设计为使得可以容易地以编程方式组合其他上下文管理器和清除功能,特别是那些是可选的或以其他方式由输入数据驱动的。</span></p><p><span class="yiyi-st" id="yiyi-77">例如,一组文件可以很容易地在一个单独的语句中处理如下:</span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">fname</span><span class="p">))</span> <span class="k">for</span> <span class="n">fname</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">]</span>
<span class="c1"># All opened files will automatically be closed at the end of</span>
<span class="c1"># the with statement, even if attempts to open files later</span>
<span class="c1"># in the list raise an exception</span>
</code></pre><p><span class="yiyi-st" id="yiyi-78">每个实例维护一堆注册的回调,当实例关闭时(以<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句结束时显式或隐式),以相反的顺序调用。</span><span class="yiyi-st" id="yiyi-79">注意,当上下文栈实例被垃圾收集时,回调是<em></em>隐式调用。</span></p><p><span class="yiyi-st" id="yiyi-80">使用此堆栈模型,以便可以正确处理在其<code class="docutils literal"><span class="pre">__init__</span></code>方法(例如文件对象)中获取其资源的上下文管理器。</span></p><p><span class="yiyi-st" id="yiyi-81">因为注册的回调以注册的相反顺序被调用,所以最终表现得好像多个嵌套<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句已经与注册的回调集合一起使用。</span><span class="yiyi-st" id="yiyi-82">这甚至扩展到异常处理 - 如果内部回调抑制或替换异常,则外部回调将基于更新的状态传递参数。</span></p><p><span class="yiyi-st" id="yiyi-83">这是一个相对较低级别的API负责正确展开退出回调栈的细节。</span><span class="yiyi-st" id="yiyi-84">它为更高级别的上下文管理器提供了一个合适的基础,它以特定应用程序的方式处理退出堆栈。</span></p><div class="versionadded"><p><span class="yiyi-st" id="yiyi-85"><span class="versionmodified">版本3.3中的新功能。</span></span></p></div><dl class="method"><dt id="contextlib.ExitStack.enter_context"><span class="yiyi-st" id="yiyi-86"> <code class="descname">enter_context</code><span class="sig-paren">(</span><em>cm</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-87">输入新的上下文管理器,并将其<a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法添加到回调栈中。</span><span class="yiyi-st" id="yiyi-88">返回值是上下文管理器自己的<a class="reference internal" href="../reference/datamodel.html#object.__enter__" title="object.__enter__"><code class="xref py py-meth docutils literal"><span class="pre">__enter__()</span></code></a>方法的结果。</span></p><p><span class="yiyi-st" id="yiyi-89">这些上下文管理器可以像通常情况下一样直接作为<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的一部分来抑制异常。</span></p></dd></dl><dl class="method"><dt id="contextlib.ExitStack.push"><span class="yiyi-st" id="yiyi-90"> <code class="descname">push</code><span class="sig-paren">(</span><em>exit</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-91">向回调栈添加上下文管理器的<a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法。</span></p><p><span class="yiyi-st" id="yiyi-92">由于<code class="docutils literal"><span class="pre">__enter__</span></code><em></em>被调用,此方法可用于覆盖<a class="reference internal" href="../reference/datamodel.html#object.__enter__" title="object.__enter__"><code class="xref py py-meth docutils literal"><span class="pre">__enter__()</span></code></a>实现的一部分,具有上下文管理器自己的<a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法。</span></p><p><span class="yiyi-st" id="yiyi-93">如果传递的不是上下文管理器的对象,此方法假定它是与上下文管理器的<a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法具有相同声明的回调,并将其直接添加到回调堆栈。</span></p><p><span class="yiyi-st" id="yiyi-94">通过返回true值这些回调可以按照上下文管理器<a class="reference internal" href="../reference/datamodel.html#object.__exit__" title="object.__exit__"><code class="xref py py-meth docutils literal"><span class="pre">__exit__()</span></code></a>方法可以抑制异常。</span></p><p><span class="yiyi-st" id="yiyi-95">传入的对象从函数返回,允许此方法用作函数装饰器。</span></p></dd></dl><dl class="method"><dt id="contextlib.ExitStack.callback"><span class="yiyi-st" id="yiyi-96"> <code class="descname">callback</code><span class="sig-paren">(</span><em>callback</em>, <em>*args</em>, <em>**kwds</em><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-97">接受一个任意的回调函数和参数,并将其添加到回调栈。</span></p><p><span class="yiyi-st" id="yiyi-98">与其他方法不同,以这种方式添加的回调不能抑制异常(因为它们从不传递异常详细信息)。</span></p><p><span class="yiyi-st" id="yiyi-99">传入的回调函数从函数返回,允许此方法用作函数装饰器。</span></p></dd></dl><dl class="method"><dt id="contextlib.ExitStack.pop_all"><span class="yiyi-st" id="yiyi-100"> <code class="descname">pop_all</code><span class="sig-paren">(</span><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-101">将回调栈传输到新的<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>实例并返回。</span><span class="yiyi-st" id="yiyi-102">这个操作不会调用回调,而是在新堆栈关闭时(在<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句结束时显式或隐式)调用它们。</span></p><p><span class="yiyi-st" id="yiyi-103">例如,一组文件可以作为“全或无”操作打开,如下所示:</span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">fname</span><span class="p">))</span> <span class="k">for</span> <span class="n">fname</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">]</span>
<span class="c1"># Hold onto the close method, but don't call it yet.</span>
<span class="n">close_files</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">pop_all</span><span class="p">()</span><span class="o">.</span><span class="n">close</span>
<span class="c1"># If opening any file fails, all previously opened files will be</span>
<span class="c1"># closed automatically. If all files are opened successfully,</span>
<span class="c1"># they will remain open even after the with statement ends.</span>
<span class="c1"># close_files() can then be invoked explicitly to close them all.</span>
</code></pre></dd></dl><dl class="method"><dt id="contextlib.ExitStack.close"><span class="yiyi-st" id="yiyi-104"> <code class="descname">close</code><span class="sig-paren">(</span><span class="sig-paren">)</span></span></dt><dd><p><span class="yiyi-st" id="yiyi-105">立即展开回调栈,按照与注册相反的顺序调用回调。</span><span class="yiyi-st" id="yiyi-106">对于注册的任何上下文管理器和退出回调,传入的参数将指示没有发生异常。</span></p></dd></dl></dd></dl></div><div class="section" id="examples-and-recipes"><h2><span class="yiyi-st" id="yiyi-107">29.6.2. </span><span class="yiyi-st" id="yiyi-108">示例和配方</span></h2><p><span class="yiyi-st" id="yiyi-109">本节描述了有效使用<a class="reference internal" href="#module-contextlib" title="contextlib: Utilities for with-statement contexts."><code class="xref py py-mod docutils literal"><span class="pre">contextlib</span></code></a>提供的工具的一些示例和配方。</span></p><div class="section" id="supporting-a-variable-number-of-context-managers"><h3><span class="yiyi-st" id="yiyi-110">29.6.2.1. </span><span class="yiyi-st" id="yiyi-111">支持可变数量的上下文管理器</span></h3><p><span class="yiyi-st" id="yiyi-112"><a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>的主要用例是在类文档中给出的:在单个<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句中支持可变数量的上下文管理器和其他清除操作。</span><span class="yiyi-st" id="yiyi-113">可变性可能来自需要由用户输入驱动的上下文管理器的数量(例如打开用户指定的容器文件),或者来自一些上下文管理器是可选的:</span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
<span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
<span class="k">if</span> <span class="n">need_special_resource</span><span class="p">():</span>
<span class="n">special</span> <span class="o">=</span> <span class="n">acquire_special_resource</span><span class="p">()</span>
<span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="n">release_special_resource</span><span class="p">,</span> <span class="n">special</span><span class="p">)</span>
<span class="c1"># Perform operations that use the acquired resources</span>
</code></pre><p><span class="yiyi-st" id="yiyi-114">如图所示,<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>也使得非常容易使用<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句来管理不原生支持上下文管理协议的任意资源。</span></p></div><div class="section" id="simplifying-support-for-single-optional-context-managers"><h3><span class="yiyi-st" id="yiyi-115">29.6.2.2. </span><span class="yiyi-st" id="yiyi-116">简化对单个可选上下文管理器的支持</span></h3><p><span class="yiyi-st" id="yiyi-117">在单个可选上下文管理器的特定情况下,<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>实例可以用作“无用”上下文管理器,允许上下文管理器被容易地省略而不影响源代码的整体结构:</span></p><pre><code class="language-python"><span></span><span class="k">def</span> <span class="nf">debug_trace</span><span class="p">(</span><span class="n">details</span><span class="p">):</span>
<span class="k">if</span> <span class="n">__debug__</span><span class="p">:</span>
<span class="k">return</span> <span class="n">TraceContext</span><span class="p">(</span><span class="n">details</span><span class="p">)</span>
<span class="c1"># Don't do anything special with the context in release mode</span>
<span class="k">return</span> <span class="n">ExitStack</span><span class="p">()</span>
<span class="k">with</span> <span class="n">debug_trace</span><span class="p">():</span>
<span class="c1"># Suite is traced in debug mode, but runs normally otherwise</span>
</code></pre></div><div class="section" id="catching-exceptions-from-enter-methods"><h3><span class="yiyi-st" id="yiyi-118">29.6.2.3. </span><span class="yiyi-st" id="yiyi-119"><code class="docutils literal"><span class="pre">__enter__</span></code>方法捕获异常</span></h3><p><span class="yiyi-st" id="yiyi-120">有时候希望从<code class="docutils literal"><span class="pre">__enter__</span></code>方法实现<em>捕获异常,而不</em>无意中从<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句体或上下文管理器的<code class="docutils literal"><span class="pre">__exit__</span></code>方法。</span><span class="yiyi-st" id="yiyi-121">通过使用<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>,上下文管理协议中的步骤可以稍微分开,以便允许:</span></p><pre><code class="language-python"><span></span><span class="n">stack</span> <span class="o">=</span> <span class="n">ExitStack</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">cm</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
<span class="c1"># handle __enter__ exception</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">with</span> <span class="n">stack</span><span class="p">:</span>
<span class="c1"># Handle normal case</span>
</code></pre><p><span class="yiyi-st" id="yiyi-122">实际上需要这样做可能表明底层API应该提供一个直接的资源管理接口供<a class="reference internal" href="../reference/compound_stmts.html#try"><code class="xref std std-keyword docutils literal"><span class="pre">try</span></code></a> / <a class="reference internal" href="../reference/compound_stmts.html#except"><code class="xref std std-keyword docutils literal"><span class="pre">except</span></code></a> / <a class="reference internal" href="../reference/compound_stmts.html#finally"><code class="xref std std-keyword docutils literal"><span class="pre">finally</span></code></a>语句但并不是所有的API都在这方面设计得很好。</span><span class="yiyi-st" id="yiyi-123">当上下文管理器是唯一提供的资源管理API时<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>可以更容易处理不能在<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句中直接处理的各种情况。</span></p></div><div class="section" id="cleaning-up-in-an-enter-implementation"><h3><span class="yiyi-st" id="yiyi-124">29.6.2.4. </span><span class="yiyi-st" id="yiyi-125"><code class="docutils literal"><span class="pre">__enter__</span></code>实现中进行清理</span></h3><p><span class="yiyi-st" id="yiyi-126"><a class="reference internal" href="#contextlib.ExitStack.push" title="contextlib.ExitStack.push"><code class="xref py py-meth docutils literal"><span class="pre">ExitStack.push()</span></code></a>文档中所述,如果<a class="reference internal" href="../reference/datamodel.html#object.__enter__" title="object.__enter__"><code class="xref py py-meth docutils literal"><span class="pre">__enter__()</span></code></a>实现中的后续步骤失败,此方法可用于清除已分配的资源。</span></p><p><span class="yiyi-st" id="yiyi-127">这里有一个例子,为接受资源获取和释放功能的上下文管理器,以及一个可选的验证功能,并将它们映射到上下文管理协议:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">contextmanager</span><span class="p">,</span> <span class="n">ExitStack</span>
<span class="k">class</span> <span class="nc">ResourceManager</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">acquire_resource</span><span class="p">,</span> <span class="n">release_resource</span><span class="p">,</span> <span class="n">check_resource_ok</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">acquire_resource</span> <span class="o">=</span> <span class="n">acquire_resource</span>
<span class="bp">self</span><span class="o">.</span><span class="n">release_resource</span> <span class="o">=</span> <span class="n">release_resource</span>
<span class="k">if</span> <span class="n">check_resource_ok</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">check_resource_ok</span><span class="p">(</span><span class="n">resource</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">check_resource_ok</span> <span class="o">=</span> <span class="n">check_resource_ok</span>
<span class="nd">@contextmanager</span>
<span class="k">def</span> <span class="nf">_cleanup_on_error</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="n">stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">yield</span>
<span class="c1"># The validation check passed and didn't raise an exception</span>
<span class="c1"># Accordingly, we want to keep the resource, and pass it</span>
<span class="c1"># back to our caller</span>
<span class="n">stack</span><span class="o">.</span><span class="n">pop_all</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">resource</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">acquire_resource</span><span class="p">()</span>
<span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cleanup_on_error</span><span class="p">():</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">check_resource_ok</span><span class="p">(</span><span class="n">resource</span><span class="p">):</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s2">"Failed validation for </span><span class="si">{!r}</span><span class="s2">"</span>
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="n">msg</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">resource</span><span class="p">))</span>
<span class="k">return</span> <span class="n">resource</span>
<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">exc_details</span><span class="p">):</span>
<span class="c1"># We don't need to duplicate any of our resource release logic</span>
<span class="bp">self</span><span class="o">.</span><span class="n">release_resource</span><span class="p">()</span>
</code></pre></div><div class="section" id="replacing-any-use-of-try-finally-and-flag-variables"><h3><span class="yiyi-st" id="yiyi-128">29.6.2.5. </span><span class="yiyi-st" id="yiyi-129">替换<code class="docutils literal"><span class="pre">try-finally</span></code>和标记变量</span></h3><p><span class="yiyi-st" id="yiyi-130">有时您会看到的模式是一个带有一个标志变量的<code class="docutils literal"><span class="pre">try-finally</span></code>语句,用于指示是否应该执行<code class="docutils literal"><span class="pre">finally</span></code>子句的主体。</span><span class="yiyi-st" id="yiyi-131">在其最简单的形式(不能只通过使用<code class="docutils literal"><span class="pre">except</span></code>子句来处理),它看起来像这样:</span></p><pre><code class="language-python"><span></span><span class="n">cleanup_needed</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">perform_operation</span><span class="p">()</span>
<span class="k">if</span> <span class="n">result</span><span class="p">:</span>
<span class="n">cleanup_needed</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">finally</span><span class="p">:</span>
<span class="k">if</span> <span class="n">cleanup_needed</span><span class="p">:</span>
<span class="n">cleanup_resources</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-132">与任何<code class="docutils literal"><span class="pre">try</span></code>基于语句的代码一样,这可能会导致开发和审查的问题,因为设置代码和清理代码可能最终被任意长的代码段分隔。</span></p><p><span class="yiyi-st" id="yiyi-133"><a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>使得可以替换地在<code class="docutils literal"><span class="pre">with</span></code>语句结束时注册执行的回调,然后稍后决定跳过执行该回调:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ExitStack</span>
<span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="n">cleanup_resources</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">perform_operation</span><span class="p">()</span>
<span class="k">if</span> <span class="n">result</span><span class="p">:</span>
<span class="n">stack</span><span class="o">.</span><span class="n">pop_all</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-134">这允许预期的清除行为被显式地提前,而不需要单独的标志变量。</span></p><p><span class="yiyi-st" id="yiyi-135">如果一个特定的应用程序使用这个模式很多,它可以通过一个小助手类进一步简化:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ExitStack</span>
<span class="k">class</span> <span class="nc">Callback</span><span class="p">(</span><span class="n">ExitStack</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">callback</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwds</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Callback</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwds</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">cancel</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">pop_all</span><span class="p">()</span>
<span class="k">with</span> <span class="n">Callback</span><span class="p">(</span><span class="n">cleanup_resources</span><span class="p">)</span> <span class="k">as</span> <span class="n">cb</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">perform_operation</span><span class="p">()</span>
<span class="k">if</span> <span class="n">result</span><span class="p">:</span>
<span class="n">cb</span><span class="o">.</span><span class="n">cancel</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-136">如果资源清理尚未整齐地捆绑到一个独立的函数中,那么仍然可以使用<a class="reference internal" href="#contextlib.ExitStack.callback" title="contextlib.ExitStack.callback"><code class="xref py py-meth docutils literal"><span class="pre">ExitStack.callback()</span></code></a>的装饰器形式来预先声明资源清理:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ExitStack</span>
<span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
<span class="nd">@stack</span><span class="o">.</span><span class="n">callback</span>
<span class="k">def</span> <span class="nf">cleanup_resources</span><span class="p">():</span>
<span class="o">...</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">perform_operation</span><span class="p">()</span>
<span class="k">if</span> <span class="n">result</span><span class="p">:</span>
<span class="n">stack</span><span class="o">.</span><span class="n">pop_all</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-137">由于装饰器协议的工作方式,以这种方式声明的回调函数不能接受任何参数。</span><span class="yiyi-st" id="yiyi-138">相反,任何要释放的资源都必须作为闭包变量访问。</span></p></div><div class="section" id="using-a-context-manager-as-a-function-decorator"><h3><span class="yiyi-st" id="yiyi-139">29.6.2.6. </span><span class="yiyi-st" id="yiyi-140">使用上下文管理器作为函数装饰器</span></h3><p><span class="yiyi-st" id="yiyi-141"><a class="reference internal" href="#contextlib.ContextDecorator" title="contextlib.ContextDecorator"><code class="xref py py-class docutils literal"><span class="pre">ContextDecorator</span></code></a>可以在普通的<code class="docutils literal"><span class="pre">with</span></code>语句中使用上下文管理器,也可以作为函数装饰器使用。</span></p><p><span class="yiyi-st" id="yiyi-142">例如,有时使用可以跟踪进入时间和退出时间的记录器来封装函数或语句组有时是有用的。</span><span class="yiyi-st" id="yiyi-143"><a class="reference internal" href="#contextlib.ContextDecorator" title="contextlib.ContextDecorator"><code class="xref py py-class docutils literal"><span class="pre">ContextDecorator</span></code></a>继承而不是为任务同时编写函数装饰器和上下文管理器,而是在单个定义中提供这两种功能:</span></p><pre><code class="language-python"><span></span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ContextDecorator</span>
<span class="kn">import</span> <span class="nn">logging</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">track_entry_and_exit</span><span class="p">(</span><span class="n">ContextDecorator</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Entering: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_type</span><span class="p">,</span> <span class="n">exc</span><span class="p">,</span> <span class="n">exc_tb</span><span class="p">):</span>
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Exiting: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">))</span>
</code></pre><p><span class="yiyi-st" id="yiyi-144">这个类的实例可以用作上下文管理器:</span></p><pre><code class="language-python"><span></span><span class="k">with</span> <span class="n">track_entry_and_exit</span><span class="p">(</span><span class="s1">'widget loader'</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Some time consuming activity goes here'</span><span class="p">)</span>
<span class="n">load_widget</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-145">也作为功能装饰器:</span></p><pre><code class="language-python"><span></span><span class="nd">@track_entry_and_exit</span><span class="p">(</span><span class="s1">'widget loader'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">activity</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Some time consuming activity goes here'</span><span class="p">)</span>
<span class="n">load_widget</span><span class="p">()</span>
</code></pre><p><span class="yiyi-st" id="yiyi-146">注意,使用上下文管理器作为函数装饰器时,还有一个额外的限制:没有办法访问<a class="reference internal" href="../reference/datamodel.html#object.__enter__" title="object.__enter__"><code class="xref py py-meth docutils literal"><span class="pre">__enter__()</span></code></a>的返回值。</span><span class="yiyi-st" id="yiyi-147">如果需要该值,那么仍然需要使用显式的<code class="docutils literal"><span class="pre">with</span></code>语句。</span></p><div class="admonition seealso"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-148">也可以看看</span></p><dl class="last docutils"><dt><span class="yiyi-st" id="yiyi-149"><span class="target" id="index-0"></span> <a class="pep reference external" href="https://www.python.org/dev/peps/pep-0343"><strong>PEP 343</strong></a> - “with”语句</span></dt><dd><span class="yiyi-st" id="yiyi-150">Python <a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的规范,背景和示例。</span></dd></dl></div></div></div><div class="section" id="single-use-reusable-and-reentrant-context-managers"><h2><span class="yiyi-st" id="yiyi-151">29.6.3. </span><span class="yiyi-st" id="yiyi-152">单次使用,可重复使用和可重入上下文管理器</span></h2><p><span class="yiyi-st" id="yiyi-153">大多数上下文管理器的写入方式意味着它们只能在<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句中有效使用一次。</span><span class="yiyi-st" id="yiyi-154">这些单用的上下文管理器必须在每次使用时重新创建 - 尝试再次使用它们将触发异常或者无法正常工作。</span></p><p><span class="yiyi-st" id="yiyi-155">这种常见限制意味着通常建议直接在<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句的头中创建上下文管理器(如上面所有的使用示例所示)。</span></p><p><span class="yiyi-st" id="yiyi-156">文件是有效使用上下文管理器的示例,因为第一个<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句将关闭文件从而阻止使用该文件对象进行任何进一步的IO操作。</span></p><p><span class="yiyi-st" id="yiyi-157">使用<a class="reference internal" href="#contextlib.contextmanager" title="contextlib.contextmanager"><code class="xref py py-func docutils literal"><span class="pre">contextmanager()</span></code></a>创建的上下文管理器也是单用的上下文管理器,并且如果尝试再次使用它们,则会报告底层生成器失败:</span></p><pre><code class="language-python"><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">contextmanager</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nd">@contextmanager</span>
<span class="gp">... </span><span class="k">def</span> <span class="nf">singleuse</span><span class="p">():</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Before"</span><span class="p">)</span>
<span class="gp">... </span> <span class="k">yield</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"After"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">cm</span> <span class="o">=</span> <span class="n">singleuse</span><span class="p">()</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">cm</span><span class="p">:</span>
<span class="gp">... </span> <span class="k">pass</span>
<span class="gp">...</span>
<span class="go">Before</span>
<span class="go">After</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">cm</span><span class="p">:</span>
<span class="gp">... </span> <span class="k">pass</span>
<span class="gp">...</span>
<span class="gt">Traceback (most recent call last):</span>
<span class="o">...</span>
<span class="gr">RuntimeError</span>: <span class="n">generator didn't yield</span>
</code></pre><div class="section" id="reentrant-context-managers"><h3><span class="yiyi-st" id="yiyi-158">29.6.3.1. </span><span class="yiyi-st" id="yiyi-159">可重入上下文管理器</span></h3><p><span class="yiyi-st" id="yiyi-160">更复杂的上下文管理器可以是“可重入的”。</span><span class="yiyi-st" id="yiyi-161">这些上下文管理器不仅可以用于多个<a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句中,也可以在里使用<em>a <a class="reference internal" href="../reference/compound_stmts.html#with"><code class="xref std std-keyword docutils literal"><span class="pre">with</span></code></a>语句,它已经使用相同的上下文经理。</em></span></p><p><span class="yiyi-st" id="yiyi-162"><a class="reference internal" href="threading.html#threading.RLock" title="threading.RLock"><code class="xref py py-class docutils literal"><span class="pre">threading.RLock</span></code></a>是可重入上下文管理器的示例,<a class="reference internal" href="#contextlib.suppress" title="contextlib.suppress"><code class="xref py py-func docutils literal"><span class="pre">suppress()</span></code></a><a class="reference internal" href="#contextlib.redirect_stdout" title="contextlib.redirect_stdout"><code class="xref py py-func docutils literal"><span class="pre">redirect_stdout()</span></code></a>也是如此。</span><span class="yiyi-st" id="yiyi-163">这里有一个非常简单的可重入使用示例:</span></p><pre><code class="language-python"><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">redirect_stdout</span>
<span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">io</span> <span class="k">import</span> <span class="n">StringIO</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">stream</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">()</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">write_to_stream</span> <span class="o">=</span> <span class="n">redirect_stdout</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">write_to_stream</span><span class="p">:</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"This is written to the stream rather than stdout"</span><span class="p">)</span>
<span class="gp">... </span> <span class="k">with</span> <span class="n">write_to_stream</span><span class="p">:</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"This is also written to the stream"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="s2">"This is written directly to stdout"</span><span class="p">)</span>
<span class="go">This is written directly to stdout</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">stream</span><span class="o">.</span><span class="n">getvalue</span><span class="p">())</span>
<span class="go">This is written to the stream rather than stdout</span>
<span class="go">This is also written to the stream</span>
</code></pre><p><span class="yiyi-st" id="yiyi-164">现实世界的重入的例子更可能涉及多个函数互相调用,因此比这个例子复杂得多。</span></p><p><span class="yiyi-st" id="yiyi-165">还要注意,可重入是<em>不是</em>与线程安全相同的东西。</span><span class="yiyi-st" id="yiyi-166"><a class="reference internal" href="#contextlib.redirect_stdout" title="contextlib.redirect_stdout"><code class="xref py py-func docutils literal"><span class="pre">redirect_stdout()</span></code></a>例如,绝对不是线程安全的,因为它通过将<a class="reference internal" href="sys.html#sys.stdout" title="sys.stdout"><code class="xref py py-data docutils literal"><span class="pre">sys.stdout</span></code></a>绑定到不同的流来对系统状态进行全局修改。</span></p></div><div class="section" id="reusable-context-managers"><h3><span class="yiyi-st" id="yiyi-167">29.6.3.2. </span><span class="yiyi-st" id="yiyi-168">可重用的上下文管理器</span></h3><p><span class="yiyi-st" id="yiyi-169">与单用和可重入上下文管理器不同的是“可重用”上下文管理器(或者,完全显式,“可重用,但不可重入”上下文管理器,因为可重用的上下文管理器也是可重用的)。</span><span class="yiyi-st" id="yiyi-170">这些上下文管理器支持多次使用但是如果特定上下文管理器实例已经在contains with语句中使用那么这些上下文管理器将失败或者否则不能正常工作</span></p><p><span class="yiyi-st" id="yiyi-171"><a class="reference internal" href="threading.html#threading.Lock" title="threading.Lock"><code class="xref py py-class docutils literal"><span class="pre">threading.Lock</span></code></a>是可重用的,但不是可重入的上下文管理器的示例(对于可重入锁,需要使用<a class="reference internal" href="threading.html#threading.RLock" title="threading.RLock"><code class="xref py py-class docutils literal"><span class="pre">threading.RLock</span></code></a>)。</span></p><p><span class="yiyi-st" id="yiyi-172">可重用但不可重入的上下文管理器的另一个示例是<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>因为在离开任何with语句时调用<em>所有</em>的回调,而不管这些回调是在哪里添加的:</span></p><pre><code class="language-python"><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ExitStack</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">stack</span> <span class="o">=</span> <span class="n">ExitStack</span><span class="p">()</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from first context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving first context"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">Leaving first context</span>
<span class="go">Callback: from first context</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from second context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving second context"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">Leaving second context</span>
<span class="go">Callback: from second context</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from outer context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="k">with</span> <span class="n">stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from inner context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving inner context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving outer context"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">Leaving inner context</span>
<span class="go">Callback: from inner context</span>
<span class="go">Callback: from outer context</span>
<span class="go">Leaving outer context</span>
</code></pre><p><span class="yiyi-st" id="yiyi-173">从示例的输出中可以看出跨多个with语句重用单个堆栈对象可以正确地工作但是尝试嵌套它们将导致堆栈在最内层with语句的末尾被清除这不太可能是期望的行为。</span></p><p><span class="yiyi-st" id="yiyi-174">使用单独的<a class="reference internal" href="#contextlib.ExitStack" title="contextlib.ExitStack"><code class="xref py py-class docutils literal"><span class="pre">ExitStack</span></code></a>实例,而不是重用单个实例避免了这个问题:</span></p><pre><code class="language-python"><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">contextlib</span> <span class="k">import</span> <span class="n">ExitStack</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">outer_stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">outer_stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from outer context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">inner_stack</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">inner_stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">"Callback: from inner context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving inner context"</span><span class="p">)</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Leaving outer context"</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">Leaving inner context</span>
<span class="go">Callback: from inner context</span>
<span class="go">Leaving outer context</span>
<span class="go">Callback: from outer context</span>
</code></pre></div></div></div></div>