Python context manager and else block

Keywords: Python encoding

Ultimately, context managers may be almost as important as subroutine s themselves. At present, we only know the fur of the context manager... Basic has with statements, and many languages have them. However, with sentences play different roles in different languages and do simple things. Although it can avoid using dots to find attributes constantly, it will not do pre-preparation and post-cleaning. Don't think the same name means the same function. The with statement is a great feature.  

                                                               -Raymond Hettinger

The eloquent Python preacher

Do this first and then do that: the else block outside the if statement

This linguistic feature is not a secret, but it has not been taken seriously: the else clause can be used not only in if statements, but also in for, while and try statements. The semantics of for/else, while/else and try/else are closely related, but quite different from if/else. At first, the meaning of the word else hindered my understanding of these features, but eventually I got used to it.

The behavior of the else clause is as follows:

for

else blocks are run only when the for loop has finished (that is, the for loop is not terminated by the break statement).

while

else blocks are run only when the while loop exits because the condition is false (that is, the while loop is not aborted by the break statement).

try

The else block is run only if there is no exception thrown in the try block. The official document (https://docs.python.org/3/reference/compound_stmts.html) also states that "exceptions thrown by the else clause will not be handled by the previous except clause."

Be careful:

In all cases, if an exception or return, break or continue statement causes control to jump outside the main block of the compound statement, the else clause will also be skipped.

 

Using the else clause in these statements usually makes the code easier to read and saves some trouble, without setting control flags or adding additional if statements.

The use of the else clause in a loop is shown in the following code snippet:

1 for item in my_list:
2     if item.flavor == 'banana':
3         break
4     else:
5         raise ValueError('No banana flavor found!')

At first, you may not feel the need to use the else clause in try/except blocks. After all, in the following code snippet, after_call() will execute only if dangerous_call() does not throw an exception, right?

1 try:
2     dangerous_call()
3     after_call()
4 except OSError:
5     log('OSError...')

However, after_call() should not be placed in the try block. In order to be clear and accurate, only statements with expected exceptions should be thrown in the try block. Therefore, it is better to write as follows:

1 try:
2     dangerous_call()
3 except OSError:
4     log('OSError...')
5 else:
6     after_call()

Now it's clear that the try block is guarding against possible errors in dangerous_call(), not after_call(). And obviously, after_call() is executed only if the try block does not throw an exception.

 

Context Manager and with Block

Context manager objects exist to manage with statements, just as iterators exist to manage for statements.

The purpose of the with statement is to simplify the try/final mode. This pattern is used to ensure that an operation is performed after a piece of code has been run, even if that piece of code is aborted due to an exception, return statement, or sys.exit() call. The code in the final clause is usually used to release important resources or restore the status of temporary changes.

The context manager protocol consists of _enter_ and _exit_ methods. When the with statement starts running, the _enter_ method is called on the context manager object. After the with statement runs, the _exit_ method is called on the context manager object to act as the final clause.

 

Demonstrating the use of file objects as context managers

>>> with open('mirror.py') as fp: # fp Bind to an open file because the file's__enter__Method returns self
...           src = fp.read(60) # from fp Read some data
...
>>> len(src)
60
>>> fp # fp Variables can still be used
<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
>>> fp.closed, fp.encoding # Can read fp Object attributes
(True, 'UTF-8')
>>> fp.read(60) # But it can't. fp Perform on I/O Operate because with At the end of the block, the call is made TextIOWrappper.__exit__Method Closes the file
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.     

Test the Looking Glass context manager class

>>> from mirror import LookingGlass
>>> with LookingGlass() as what: # The context manager is an instance of the LookingGlass class; Python calls the _enter_ method on the context manager to bind the return result to what
...     print('Alice, Kitty and Snowdrop') # Print a string, and then print the value of what variable
...     print(what)
...
pordwonS dna yttiK ,ecilA # The printed content is reversed.
YKCOWREBBAJ
>>> what # Now that the with block has been executed, you can see that the value returned by the _enter_ method, that is, the value stored in what variable is the string'JABBERWOCKY'
'JABBERWOCKY'
>>> print('Back to normal.') # The output is no longer reverse.
Back to normal.

Mir.py: Looking Glass Context Manager class code

 1 class LookingGlass:
 2 
 3     def __enter__(self):                        # except self Outside, Python call__enter__Method does not intrude into other parameters
 4         import sys
 5         self.original_write = sys.stdout.write  # Original sys.stdout.write Method is stored in an instance property for later use
 6         sys.stdout.write = self.reverse_write   # by sys.stdout.write Make a monkey patch and replace it with your own way of writing
 7         return 'JABBERWOCKY'                    # Return 'JABBERWOCKY' String, so that content is stored in the target variable what
 8 
 9     def reverse_write(self, text):             # This is for replacement. sys.stdout.write The way to do this is to text The content of the parameter is inverted, and then the original method is invoked to implement it.
10         return self.original_write(text[::-1])
11 
12 
13     def __exit__(self, exc_type, exc_val, traceback):   # If everything is all right, Python Would call__exit__The parameters passed in by the method are three None,If an exception is thrown, the three parameters are the data of the exception
14         import sys
15         sys.stdout.write = self.original_write  # Restore to original sys.studout.write Method
16         if exc_type is ZeroDivisionError:       # If there is an exception, and yes ZeroDivisionError Type, print a message
17             print('Please DO NOT divide by zero!')  
18             return True                         # Then return True,Tell the interpreter that the exception has been handled

When the interpreter calls the _enter_ method, no parameters are passed in except the implicit self. The three parameters passed to the _exit_ method are listed below.

exc_type

Exception classes (e.g. ZeroDivision Error)

exc_value

Exception instances. Sometimes parameters are passed to exception constructors, such as error messages, which can be retrieved using exc_value.args

traceback

traceback object

 

Use the LookingGlass class outside of the with block

>>> from mirror import LookingGlass
>>> manager = LookingGlass() # Instantiate and review the manager instance, equivalent to with Looking Glass () as manager
>>> manager
<mirror.LookingGlass object at 0x2a578ac>
>>> monster = manager.__enter__() # Call the _enter_() method in context manager to store the results in monster
>>> monster == 'JABBERWOCKY' # The value of monster is the string'JABBERWOCKY', and the printed True identifier is reversed because the monkey patch is used.
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>ca875a2x0 ta tcejbo ssalGgnikooL.rorrim<
>>> manager.__exit__(None, None, None) # Call manager. _ exit _ to restore to the previous stdout.write
>>> monster
'JABBERWOCKY'

 

Practical Tools in contextlib Module

closing

If the object provides a close() method but does not implement the _enter_/_exit_ protocol, you can use this function to build a context manager.

suppress

Build a context manager that temporarily ignores the specified exception.

@contextmanager

This decorator transforms a simple generator function into a context manager, so that you don't need to create classes to implement the manager protocol.

ContextDecorator 

This is a base class for defining a class-based context manager. This context manager can also be used to decorate functions and run the entire function in a managed context.

ExitStack

This context manager can access multiple context managers. At the end of the with block, ExitStack calls the _exit_ method of each context manager on the stack in a last-in, first-out order. This class can be used if you don't know in advance how many context managers the with block will enter. For example, open all files in any file list at the same time.

 

Use @contextmanager

@ The contextmanager decorator reduces the amount of boilerplate code needed to create a context manager, because instead of writing a complete class to define _enter_ and _exit_ methods, it only needs to implement a generator of yield statements to generate the values it wants the _enter_ method to return.

In the generator decorated with @contextmanager, the function of the yield statement is to divide the definition body of the function into two parts: all the code before the yield statement is executed at the beginning of the with block (that is, when the interpreter calls the _enter_ method), and the code after the yield statement is executed at the end of the with block (that is, when the _exit_ method is called).

mirror_gen.py: Context Manager Implemented with Generator

 1 import contextlib
 2 
 3 
 4 @contextlib.contextmanager              # application contextmanager Decorator
 5 def looking_glass():
 6     import sys
 7     original_write = sys.stdout.write   # Store the original sys.stdout.write Method
 8 
 9     def reverse_write(text):            # Define custom reverse_write Function; accessible in closures original_write
10         original_write(text[::-1])
11 
12     sys.stdout.write = reverse_write    # hold sys.stdout.write replace with reverse_write
13     yield 'JABBERWOCKY'                 # Output a value, which is bound to with Statement as On the target variables of clauses
14     sys.stdout.write = original_write   # Once the control right jumps out with Block, continue execution yield The code after the statement; here it is restored to the original sys. stdout.write Method
15 
16 with looking_glass() as what:           # Direct implementation through context manager with Function
17     print('Alice, Kitty and Snowdrop')
18     print(what)
19 
20 print(what)

The results of the above code execution are as follows:

pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
JABBERWOCKY

In fact, the contextlib.contextmanager decorator wraps functions into classes that implement _enter_ and _exit_ methods

The _enter_ method of this class has the following functions:

(1) Call the generator function and save the generator object (hereinafter referred to as gen).

(2) Call next(gen) to execute where the yield keyword is.

(3) Returns the value of the next(gen) output to bind the output value to the target variable in the with/as statement.

When the with block terminates, the _exit_ method does the following things:

(1) Check if an exception is passed to exc_type; if so, call gen.throw(exception) to throw an exception on the line containing the yield keyword in the generator function definition body.

(2) Otherwise, call next(gen) to continue executing the code after the yield statement in the generator function definition body.

Note:

There's a serious mistake in above: if an exception is thrown in the with block, the Python interpreter captures it and then throws it again in the yield expression of the look_glass function. However, there is no code that handles errors, so the look_glass function will stop and will never be restored to the original sys.stdout.write method, resulting in the system in an invalid state.

mirror_gen_exc.py: Generator-based context manager and exception handling

 1 import contextlib
 2 
 3 
 4 @contextlib.contextmanager
 5 def looking_glass():
 6     import sys
 7     original_write = sys.stdout.write
 8 
 9     def reverse_write(text):
10         original_write(text[::-1])
11 
12     sys.stdout.write = reverse_write
13     msg = ''                                #Create a variable to save possible error messages.
14     try:
15         yield 'JABBERWOCKY'
16     except ZeroDivisionError:               #Handle ZeroDivisionError Exception, set an error message
17         msg = 'Please DO NOT divide by zero!'
18     finally:
19         sys.stdout.write = original_write   # Revoke pair sys.stdout.write Method The Monkey Patch
20         if msg:
21             print(msg)                      # If an error message is set, print it out.

Be careful:

When using the @contextmanager decorator, it is unavoidable to place the yield statement in the try/final statement (or in the with statement), because we never know what the user of the context manager will do in the with block.

Posted by Smee on Thu, 30 May 2019 09:53:23 -0700