A series of actions are often placed in a block of statements when writing Python code:
When a condition is true – execute this block of statements
When a condition is true – loop execution of this statement block
Sometimes we need to keep a state when the program is running in a block of statements, and end this state after leaving the statement block.
So, in fact, the task of the context manager is-to prepare the code block before execution, and to clean up after the code block executes.
2. How do I use the context Manager?
Look at the code is the best way to learn, to see how we usually open a file and write "Hello world"?
' My_file.txt ' ' W ' # Mode that allows to write to the file writer = open (filename, mode) writer.write (") Writer.write ('World')
1-2 lines, we indicate the file name and open mode (write).
Line 3rd, open the file, 4-5 lines write "Hello World", line 6th closes the file.
That's fine, why do you need a context manager? But we overlooked a small but important detail: What if we didn't get a chance to close the file on line 6th?
For example, the disk is full, so we throw an exception when we try to write to the file on line 4th, and line 6th does not have a chance to execute at all.
Of course, we can use the try-finally statement block to wrap:
writer = open (filename, mode)try: writer.write (") writer.write ('World') finally : writer.close ()
The code in the finally statement block executes regardless of what happens in the try statement block. Therefore, the file must be closed. What's the problem with this? Of course not, but when we do something more complicated than writing "Hello World", the try-finally statement becomes ugly. For example, to open two files, one to read and one to write, and two to copy between files, the With statement ensures that both can be closed at the same time.
OK, let's break it down:
First, create a file variable named "writer".
Then, perform some action on the writer.
Finally, close writer.
Isn't that a lot more elegant?
with open (filename, mode) as Writer: Writer.write (") writer.write ('World')
Let's go a little deeper, "with" is a new keyword and always comes with the context manager. "Open (filename, mode)" was present in the previous code. "As" is another keyword that refers to what is returned from the "open" function and assigns it to a new variable. "Writer" is a new variable name.
2-3 line, indent to open a new block of code. In this block of code, we are able to do arbitrary work with writer. This allows us to use the "open" context Manager, which ensures that our code is both elegant and secure. It accomplished the task of try-finally well.
The open function can be used both as a simple function and as a context manager. This is because the open function returns a file type variable, and this file type implements the Write method we used earlier, but you also have to implement some special methods as the context manager, which I'll cover in the next section.
3. Custom Context Manager
Let's write an "open" context manager.
To implement the context manager, two methods must be implemented – one to prepare for entry into the statement block, and another to leave the statement block for aftercare operations. At the same time, we need two parameters: file name and open mode.
The Python class contains two special methods named: __enter__ and __exit__ (double underscore as prefix and suffix).
When an object is used as a context manager:
The __enter__ method is called before it enters the code block.
The __exit__ method is called after it leaves the code block (even if an exception is encountered in the code block).
The following is an example of a context manager that prints when it enters and leaves a block of code, respectively.
classPypixcontextmanagerdemo:def __enter__(self):Print 'Entering The Block' def __exit__(Self, *unused):Print 'Exiting The Block'With Pypixcontextmanagerdemo ():Print 'In the block' #Output:#Entering The Block#In the block#Exiting The Block
Note some things:
- No arguments were passed.
- "As" keyword is not used here.
- We'll discuss the parameter settings for the __exit__ method later.
How do we pass parameters to a class? In fact, in any class, you can use the __init__ method, where we will rewrite it to receive two necessary parameters (filename, mode).
When we enter a block of statements, we will use the open function, as in the first example. And when we leave the block of statements, everything that opens in the __ENTER__ function is closed.
Here's our code:
classPypixopen:def __init__(self, filename, mode): Self.filename=filename Self.mode=Modedef __enter__(self): Self.openedfile=Open (Self.filename, Self.mode)returnSelf.openedfiledef __exit__(Self, *unused): Self.openedFile.close () with pypixopen (filename, mode) as Writer:writer.write ("Hello World from our new Context manager!")
To see what's changed:
3-5 lines, two parameters are received via __init__.
7-9 lines, open the file and return.
12 lines, closes the file when you leave the statement block.
14-15 lines, imitate open using our own context manager.
In addition, there are a few things to emphasize:
How to handle exceptions
We completely ignore the possible problems inside the statement block.
If an exception occurs inside a statement block, the __exit__ method is called and the exception is re-thrown (re-raised). When processing a file write operation, you certainly do not want to hide these exceptions for most of the time, so this is possible. For exceptions that you do not want to re-throw, we can let the __exit__ method simply return true to ignore any exceptions that occur in the statement block (which is not advisable in most cases).
We can learn more detailed information when an exception occurs, and a complete __exit__ function signature should look like this:
def __exit__ (Self, Exc_type, Exc_val, EXC_TB)
This allows the __exit__ function to get all the information about the exception (exception type, outliers, and exception tracking information) that will help with the exception handling operation. Here I will not discuss in detail how exception handling should be written, here is an example, only responsible for throwing syntaxerrors exceptions.
class raiseonlyifsyntaxerror: def __enter__ (self): Pass def __exit__ (self, exc_type, Exc_val, EXC_TB): return SyntaxError! = Exc_type
4. Talk about the content of the context library (CONTEXTLIB)
Contextlib is a Python module that provides an easier-to-use context manager.
Contextlib.closing
Let's say we have a CREATE DATABASE function that returns a database object and closes the related resource (database connection session, etc.) after use.
We can handle it as before or through the context Manager:
With Contextlib.closing (CreateDatabase ()) as database: database.query ()
The Contextlib.closing method calls the database shutdown method after the statement block ends.
contextlib.nested
Another cool feature effectively helps us to reduce nesting:
Suppose we have two files, one to read, one to write, and a copy to make.
The following are not advocated:
With open ('toreadfile'R') as reader: with open ( 'towritefile'w') as Writer: Writer.writer (Reader.read ())
can be simplified by contextlib.nested:
With contextlib.nested (open ('fileToRead.txt'R'), Open ('fileToWrite.txt'w')) as ( Reader, writer): writer.write (Reader.read ())
In Python2.7, this notation is replaced by a new syntax:
With open ('fileToRead.txt'R') as Reader, open ( 'fileToWrite.txt'w') as Writer: Writer.write (Reader.read ())
Contextlib.contextmanager
For Python advanced players, any function that can be split into two parts of the yield keyword can be achieved through the context manager of the adorner decoration. any content before yield can be seen as an operation before the code block executes, and any post-yield operation can be placed in the Exit Function .
Here I give an example of a thread lock:
The lock mechanism ensures that two pieces of code do not interfere with each other while executing simultaneously. For example, we have two pieces of code that execute concurrently and write a file, and then we get a mix of two input error files. But if we can have a lock, any code that wants to write a file must first get the lock, then it's a good thing. If you want to learn more about concurrent programming, please refer to the relevant literature.
The following is an example of a thread-safe write function:
Import=def safewritetofile (openedfile, content): lock.acquire () Openedfile.write (content) lock.release ()
Next, let's implement the context manager to recall the previous analysis of yield and contextlib:
@contextlib. ContextManagerdefLoudlock ():Print 'Locking'Lock.acquire ()yield Print 'Releasing'lock.release () with Loudlock ():Print 'Lock is locked:%s'%lock.locked ()Print 'Doing something that needs locking' #Output:#Locking#Lock is locked:true#Doing something that needs locking#Releasing
In particular, this is not the wording of the exception safe (exception safe). If you want to ensure exceptional security, use the TRY statement for yield. Fortunately, threading. Lock is already a context manager, so we just need to simply:
@contextlib. ContextManager def Loudlock (): Print ' Locking ' With Lock: yield print'releasing'
Because Threading.lock returns false through the __EXIT__ function when an exception occurs, this will be re-thrown when yield is called. In this case the lock will be freed, but the call to "print ' releasing '" will not be executed unless we rewrite try-finally.
If you want to use the "as" keyword in the context manager, use yield to return the value you want, which will be assigned to the new variable by using the AS keyword.
Python context Manager