From ce4d3a4f9b79a574e24c467abbf0c36352f57971 Mon Sep 17 00:00:00 2001 From: Mark Pilgrim Date: Sun, 16 Aug 2009 16:53:41 -0400 Subject: [PATCH] expand on nested with blocks --- files.html | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/files.html b/files.html index 326eb72..82134ae 100644 --- a/files.html +++ b/files.html @@ -501,19 +501,23 @@ C

Let’s take the last part first. -


-print('A')                                                                             
-with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):  
-    print('B')                                                                         
-print('C')                                                                             
-
    -
  1. This will print to the IDE “Interactive Window” (or the terminal, if running the script from the command line). -
  2. This is a with statement, which you’ve seen before. But unlike all previous example, this one doesn’t stop at as a_file. Instead, there’s a comma and another function call. The with statement can actually take a comma-separated list of contexts. The first is a context you’ve seen several times already: it opens a file, assigns the stream object to a_file, and closes the file automatically when the context ends. The second context is a custom-built context that redirects sys.stdout to the stream object that was created in the first context. -
  3. Because this print() statement is executed with the contexts created by the with statement, it will not print to the screen; it will write to the file out.log. -
  4. The with 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 sys.stdout back to its original value. That means that this call to the print() function will once again print to the screen. -
+
print('A')
+with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
+    print('B')
+print('C')
-

Now take a look at the RedirectStdoutTo class. It is a custom context manager. Upon entering the context, it redirects sys.stdout to a given stream object. Upon exiting the context, it restores sys.stdout to its original value. +

That’s a complicated with statement. Let me rewrite it as something more recognizable. + +

with open('out.log', mode='w', encoding='utf-8') as a_file:
+    with RedirectStdoutTo(a_file):
+        print('B')
+ +

As the rewrite shows, you actually have two with statements, one nested within the scope of the other. The “outer” with statement should be familiar by now: it opens a UTF-8-encoded text file named out.log for writing and assigns the stream object to a variable named a_file. But that’s not the only thing odd here. +

with RedirectStdoutTo(a_file):
+ +

Where’s the as clause? The with statement doesn’t actually require one. Just like you can call a function and ignore its return value, you can have a with statement that doesn’t assign the with context to a variable. In this case, you’re only interested in the side effects of the RedirectStdoutTo context. + +

What are those side effects? Take a look inside the RedirectStdoutTo class. This class is a custom context manager. Any class can be a context manager by defining two special methods: __enter__() and __exit__().

class RedirectStdoutTo:
     def __init__(self, out_new):    
@@ -531,6 +535,20 @@ C
 
  • The __exit__() method is another special class method; Python calls it when exiting the context (i.e. at the end of the with statement). This method restores standard output to its original value by assigning the saved self.out_old value to sys.stdout. +

    Putting it all together: + +

    
    +print('A')                                                                             
    +with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):  
    +    print('B')                                                                         
    +print('C')                                                                             
    +
      +
    1. This will print to the IDE “Interactive Window” (or the terminal, if running the script from the command line). +
    2. This with statement takes a comma-separated list of contexts. The comma-separated list acts like a series of nested with 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 sys.stdout to the stream object that was created in the first context. +
    3. Because this print() statement is executed with the contexts created by the with statement, it will not print to the screen; it will write to the file out.log. +
    4. The with 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 sys.stdout back to its original value, then the first context closed the file named out.log. Since standard out has been restored to its original value, calling the print() function will once again print to the screen. +
    +

    Redirecting standard error works exactly the same way, using sys.stderr instead of sys.stdout.