mirror of
https://github.com/kennethreitz/dive-into-python3.git
synced 2026-06-05 15:00:18 +00:00
expand on nested with blocks
This commit is contained in:
+30
-12
@@ -501,19 +501,23 @@ C</samp>
|
||||
|
||||
<p>Let’s take the last part first.
|
||||
|
||||
<pre class=pp><code>
|
||||
<a>print('A') <span class=u>①</span></a>
|
||||
<a>with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file): <span class=u>②</span></a>
|
||||
<a> print('B') <span class=u>③</span></a>
|
||||
<a>print('C') <span class=u>④</span></a></code></pre>
|
||||
<ol>
|
||||
<li>This will print to the <abbr>IDE</abbr> “Interactive Window” (or the terminal, if running the script from the command line).
|
||||
<li>This is <a href=#with>a <code>with</code> statement</a>, which you’ve seen before. But unlike all previous example, this one doesn’t stop at <code>as a_file</code>. Instead, there’s a comma and another function call. The <code>with</code> statement can actually take <em>a comma-separated list of contexts</em>. The first is a context you’ve seen several times already: it opens a file, assigns the stream object to <var>a_file</var>, and closes the file automatically when the context ends. The second context is a custom-built context that redirects <code>sys.stdout</code> to the stream object that was created in the first context.
|
||||
<li>Because this <code>print()</code> statement is executed with the contexts created by the <code>with</code> statement, it will not print to the screen; it will write to the file <code>out.log</code>.
|
||||
<li>The <code>with</code> code block is over. Python has told each context manager to do whatever it is they do upon exiting a context. The first context closed the file; the second context changed <code>sys.stdout</code> back to its original value. That means that this call to the <code>print()</code> function will once again print to the screen.
|
||||
</ol>
|
||||
<pre class=pp><code>print('A')
|
||||
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
|
||||
print('B')
|
||||
print('C')</code></pre>
|
||||
|
||||
<p>Now take a look at the <code>RedirectStdoutTo</code> class. It is a custom context manager. Upon entering the context, it redirects <code>sys.stdout</code> to a given stream object. Upon exiting the context, it restores <code>sys.stdout</code> to its original value.
|
||||
<p>That’s a complicated <code>with</code> statement. Let me rewrite it as something more recognizable.
|
||||
|
||||
<pre class=pp><code>with open('out.log', mode='w', encoding='utf-8') as a_file:
|
||||
with RedirectStdoutTo(a_file):
|
||||
print('B')</code></pre>
|
||||
|
||||
<p>As the rewrite shows, you actually have <em>two</em> <code>with</code> statements, one nested within the scope of the other. The “outer” <code>with</code> statement should be familiar by now: it opens a <abbr>UTF-8</abbr>-encoded text file named <code>out.log</code> for writing and assigns the stream object to a variable named <var>a_file</var>. But that’s not the only thing odd here.
|
||||
<pre class='nd pp'><code>with RedirectStdoutTo(a_file):</code></pre>
|
||||
|
||||
<p>Where’s the <code>as</code> clause? The <code>with</code> statement doesn’t actually require one. Just like you can call a function and ignore its return value, you can have a <code>with</code> statement that doesn’t assign the <code>with</code> context to a variable. In this case, you’re only interested in the side effects of the <code>RedirectStdoutTo</code> context.
|
||||
|
||||
<p>What are those side effects? Take a look inside the <code>RedirectStdoutTo</code> class. This class is a custom <a href=special-method-names.html#context-managers>context manager</a>. Any class can be a context manager by defining two <a href=iterators.html#a-fibonacci-iterator>special methods</a>: <code>__enter__()</code> and <code>__exit__()</code>.
|
||||
|
||||
<pre class=pp><code>class RedirectStdoutTo:
|
||||
<a> def __init__(self, out_new): <span class=u>①</span></a>
|
||||
@@ -531,6 +535,20 @@ C</samp>
|
||||
<li>The <code>__exit__()</code> method is another special class method; Python calls it when exiting the context (<i>i.e.</i> at the end of the <code>with</code> statement). This method restores standard output to its original value by assigning the saved <var>self.out_old</var> value to <var>sys.stdout</var>.
|
||||
</ol>
|
||||
|
||||
<p>Putting it all together:
|
||||
|
||||
<pre class=pp><code>
|
||||
<a>print('A') <span class=u>①</span></a>
|
||||
<a>with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file): <span class=u>②</span></a>
|
||||
<a> print('B') <span class=u>③</span></a>
|
||||
<a>print('C') <span class=u>④</span></a></code></pre>
|
||||
<ol>
|
||||
<li>This will print to the <abbr>IDE</abbr> “Interactive Window” (or the terminal, if running the script from the command line).
|
||||
<li>This <a href=#with><code>with</code> statement</a> takes <em>a comma-separated list of contexts</em>. The comma-separated list acts like a series of nested <code>with</code> blocks. The first context listed is the “outer” block; the last one listed is the “inner” block. The first context opens a file; the second context redirects <code>sys.stdout</code> to the stream object that was created in the first context.
|
||||
<li>Because this <code>print()</code> statement is executed with the contexts created by the <code>with</code> statement, it will not print to the screen; it will write to the file <code>out.log</code>.
|
||||
<li>The <code>with</code> code block is over. Python has told each context manager to do whatever it is they do upon exiting a context. The context managers form a last-in-first-out stack. Upon exiting, the second context changed <code>sys.stdout</code> back to its original value, then the first context closed the file named <code>out.log</code>. Since standard out has been restored to its original value, calling the <code>print()</code> function will once again print to the screen.
|
||||
</ol>
|
||||
|
||||
<p>Redirecting standard error works exactly the same way, using <code>sys.stderr</code> instead of <code>sys.stdout</code>.
|
||||
|
||||
<p class=a>⁂
|
||||
|
||||
Reference in New Issue
Block a user