SoFunction
Updated on 2024-11-17

An in-depth look at context managers and else blocks in Python

preamble

This article introduces the Python context manager and else block related content, shared for your reference and learning, the following words do not say much, come together to see the details of the introduction.

Before we begin, let's take a look at the following passage:

Eventually, the context manager may be almost as important as the subroutine itself. For now, we have only scratched the surface of context managers ......Basic languages have with statements, and many do. However, the with statement does different things in various languages and does simple things that, while avoiding the constant use of dot notation to look up attributes, don't do prep and cleanup afterward. Don't think that just because it has the same name, it means it does the same thing. with statements are amazing features.

——Raymond Hettinger

The Eloquent Python Evangelist

Do this, then do that: else blocks outside of if statements

It's no secret that this language feature is underappreciated: the else clause can be used not only in if statements, but also in for, while, and try statements. for/else, while/else, and try/else are closely semantically related, though very 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

The else block is run only when the for loop is complete (i.e., the for loop is not aborted by a break statement).

while

The else block is run only if the while loop exits because the condition is false (i.e., the while loop is not aborted by a break statement).

try

Run the else block only if no exceptions are thrown in the try block. The official documentation (/3/reference/compound_stmts.html) also states, "Exceptions thrown by the else clause are not handled by the preceding except clause."

Attention:

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

Using else clauses in these statements usually makes the code easier to read and saves you the trouble of having to set control flags or add extra if statements.

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

 for item in my_list:
  if  == 'banana':
   break
  else:
   raise ValueError('No banana flavor found!')

At first, you might not think it's necessary to use an else clause in a try/except block. After all, in the following code snippet, only thedangerous_call() without throwing an exception.after_call() before it's executed, right?

 try:
  dangerous_call()
  after_call()
 except OSError:
  log('OSError...')

However.after_call() It should not be in a try block. For the sake of clarity and accuracy, only statements that throw the expected exception should be in the try block. Therefore, it is better to write it like the following:

 try:
  dangerous_call()
 except OSError:
  log('OSError...')
 else:
  after_call()

Now it's clear that the try block defense isdangerous_call() Possible errors, rather thanafter_call() The try block will only be executed if it doesn't throw an exception. And obviously, the try block will only be executed if it doesn't throw an exception.after_call()

Context manager and with block

The context manager object exists to manage with statements, just as iterators exist to manage for statements.

The purpose of the with statement is to simplify the try/finally pattern. This pattern is used to ensure that an action is performed after a piece of code has finished running, even if that piece of code has been damaged by an exception, return statement, or() The call is aborted, and the specified action is performed. the code in the finally clause is often used to free important resources or to restore temporarily changed state.

The context manager protocol contains two methods, __enter__ and __exit__. the __enter__ method is called on the context manager object at the start of the with statement. the __exit__ method is called on the context manager object at the end of the with statement, playing the role of the finally clause.

🌰 Demonstrate using a file object as a context manager

>>> with open('') as fp: # fp binds to an open file because the file's __enter__ method returns self
...   src = (60) # Read some data from fp
...
>>> len(src)
>>> fp # fp variables can still be used
<_io.TextIOWrapper name='' mode='r' encoding='UTF-8'>
>>> ,  # Can read properties of fp objects
(True, 'UTF-8')
>>> (60) # But you can't perform I/O operations on the fp because at the end of the with block, the TextIOWrappper.__exit__ method is called to close the file
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

Testing the LookingGlass 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 returned result to what
...  print('Alice, Kitty and Snowdrop') # Print a string and then print the value of the what variable
...  print(what)
...
pordwonS dna yttiK ,ecilA # The printout is reversed
YKCOWREBBAJ
>>> what # Now that the with block has been executed, you can see that the value returned by the __enter__ method, i.e. the value stored in the what variable, is the string 'JABBERWOCKY'
'JABBERWOCKY'
>>> print('Back to normal.') # The output is no longer reversed
Back to normal.

: Code for the LookingGlass context manager class

class LookingGlass:

 def __enter__(self):      # Python calls the __enter__ method with no arguments other than self.
  import sys
  self.original_write =  # Save the original method in an instance property for later use
   = self.reverse_write # for Monkey patch, replace with your own method.
  return 'JABBERWOCKY'     # Return the 'JABBERWOCKY' string so that the contents are stored in the target variable what

 def reverse_write(self, text):    # This is the method to use instead of reversing the content of the text parameter and calling the original method to implement it
  return self.original_write(text[::-1])


 def __exit__(self, exc_type, exc_val, traceback): # If everything is fine, Python calls the __exit__ method passing in three None arguments, and if an exception is thrown, the three arguments are the exception data
  import sys
   = self.original_write # Restore to original method
  if exc_type is ZeroDivisionError:  # If there is an exception and it is of type ZeroDivisionError, print a message
   print('Please DO NOT divide by zero!') 
   return True       # Then return to True,interpreter,Exception has been handled

When the interpreter calls the __enter__ method, it does not pass any arguments except implicitly self. The three arguments passed to the __exit__ method are listed below.

exc_type

Exception classes (e.g. ZeroDivisionError)

exc_value

Exception instances. Sometimes there are parameters passed to the exception constructor, such as an error message, which can be obtained using exc_value.args

traceback

traceback object

Using the LookingGlass Class Outside of a with Block

>>> from mirror import LookingGlass
>>> manager = LookingGlass() # Instantiate and review manager instances, equivalent to with LookingGlass() as manager
>>> manager
< object at 0x2a578ac>
>>> monster = manager.__enter__() # Call the __enter__() method in the context manager and store the result in monster
>>> monster == 'JABBERWOCKY' # monster's value is the string 'JABBERWOCKY', the printed True identifier is reversed because of the monkey patch used
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>ca875a2x0 ta tcejbo <
>>> manager.__exit__(None, None, None) # Call manager.__exit__ to revert back to the previous
>>> monster
'JABBERWOCKY'

Utilities in the contextlib module

closing

If the object provides theclose() method, but does not implement the __enter__/__exit__ protocol, then this function can be used to build the context manager.

suppress

Constructs a context manager that temporarily ignores the specified exception.

@contextmanager

This decorator turns simple generator functions into context managers so that you don't have 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, running the entire function in a managed context.

ExitStack

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

Using @contextmanager

The @contextmanager decorator reduces the amount of sample code needed to create a context manager because instead of writing a complete class defining the __enter__ and __exit__ methods, you can simply implement a generator with a yield statement that generates the value you want the __enter__ method to return.

In a generator decorated with @contextmanager, the yield statement serves to split the body of the function definition into two parts: all code preceding the yield statement is executed at the beginning of the with block (when the interpreter calls the __enter__ method), and the code following the yield statement is executed at the end of the with block (when the __exit__ method is called). method) at the beginning of the with block, and the code following the yield statement is executed at the end of the with block.

mirror_gen.py: context manager implemented using generators

import contextlib


@    # Apply contextmanager decorator
def looking_glass():
 import sys
 original_write =  # Storage of original method

 def reverse_write(text):   # Define custom reverse_write function; original_write is accessible in closure
  original_write(text[::-1])

  = reverse_write # Replace with reverse_write
 yield 'JABBERWOCKY'     # produces a value that is bound to the target variable of the as clause of the with statement
  = original_write # Once control jumps out of the with block, execution continues after the yield statement; here it reverts to the original sys. method.

with looking_glass() as what:   # Implementing with directly through the context manager
 print('Alice, Kitty and Snowdrop')
 print(what)

print(what)

The result of the above code execution is:

pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
JABBERWOCKY

In fact, the decorator wraps the function in a class that implements the __enter__ and __exit__ methods.

The __enter__ method of this class does the following.

(1) Call the generator function to save the generator object (which is referred to here as gen).

(2) Call next(gen), which executes to the location of the yield keyword.

(3) Returns the value produced by next(gen) so that the output can be bound to the target variable in the with/as statement.

When the with block terminates, the __exit__ method does the following.

(1) Check that no exceptions have been passed to exc_type; if so, call the(exception) , throws an exception on the line containing the yield keyword in the body of the generator function definition.

(2) Otherwise, callnext(gen) , continue executing the code after the yield statement in the body of the generator function definition.

Attention: 

The 🌰 above has a serious bug: if an exception is thrown in a with block, the Python interpreter catches it and then throws it again in the yield expression of the looking_glass function. However, there's no code there to handle the error, so the looking_glass function aborts and never reverts back to the original method, causing the system to be in an invalid state.

mirror_gen_exc.py: generator-based context manager, but also implements exception handling

import contextlib


@
def looking_glass():
 import sys
 original_write = 

 def reverse_write(text):
  original_write(text[::-1])

  = reverse_write
 msg = ''        # Create a variable to hold possible error messages;
 try:
  yield 'JABBERWOCKY'
 except ZeroDivisionError:    #Handle ZeroDivisionError exception, set an error message
  msg = 'Please DO NOT divide by zero!'
 finally:
   = original_write # Undo monkey patches made to methods
  if msg:
   print(msg)      # If an error message is set,Print it out.

Attention:

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

summarize

The above is the entire content of this article, I hope that the content of this article on your learning or work can bring some help, if there are questions you can leave a message to exchange, thank you for my support.