In Python, calling the __enter__ method before entering the code block and calling the object of the __exit__ method after leaving the code block as the context manager, we'll dive into the context manager in Python to see the role and usage of the context Manager:
1. What is the context manager?
For example, when you write Python code, you often put a series of actions in a block of statements:
(1) When a condition is true – execute this block of statements
(2) 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.
The context manager is a feature added to Python2.5 that makes your code more readable and less error-sensitive. Next, let's take a look at how to use it.
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"?
filename = ' my_file.txt ' mode = ' W ' # mode, allows to write to the FileWriter = open (filename, mode) writer.write (' Hello ') Writer.write (' World ') writer.close ()
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 (' hello ') 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:
(1) First, create a file variable named "writer".
(2) Then, perform some operations on the writer.
(3) Finally, close writer.
Isn't that a lot more elegant?
with open (filename, mode) as Writer: writer.write (' hello ') 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:
(1) The __enter__ method is called before it enters the code block.
(2) 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.
Class Pypixcontextmanagerdemo: 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 t He block#exiting the block
Note some things:
(1) No parameters were passed.
(2) "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:
Class Pypixopen: def __init__ (self, filename, mode): self.filename = filename self.mode = mode def __ Enter__ (self): self.openedfile = open (Self.filename, Self.mode) return self.openedfile def __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:
(1) 3-5 lines, received two parameters via __init__.
(2) 7-9 lines, open the file and return.
(3) 12 lines, close the file when you leave the statement block.
(4) 14-15 lines, imitating open using our own context manager.
In addition, there are a few things to emphasize:
4. 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
Catching Exceptions:
When an exception is thrown in the With block, it is passed as a parameter to __exit__. Three parameters are used, and sys.exc_info () returns the same: type, value, and backtracking (traceback). When no exception is thrown, three parameters are none. The context manager can "swallow" an exception by returning a true (true) value from __exit__. Exceptions can be easily ignored, because if __exit__ does not use return to end directly, return none--a false (false) value, and then re-throw after the end of the __exit__.
The ability to catch exceptions creates interesting possibilities. A classic example from unit testing--we want to make sure some code throws the right kind of exception:
Class Assert_raises (object): # based on Pytest and UnitTest. TestCase def __init__ (self, type): Self.type = Type def __enter__ (self): pass def __exit__ (self, Type, value, Traceback): If Type is None: raise Assertionerror (' exception expected ') if Issubclass (type, Self.type): return True # Swallow The expected exception raise Assertionerror (' wrong exception type ') with Assert_raises (keyerror): {}[' foo ']
5. Talk about the content of the context library (CONTEXTLIB)
Contextlib is a Python module that provides an easier-to-use context manager.
(1) 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.
(2) 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 following is an example of a thread-safe write function:
Import Threading Lock = Threading. Lock () 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. Contextmanagerdef loudlock (): print ' Locking ' lock.acquire () yield print ' releasing ' lock.release () with Loudlock (): print ' lock was 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. Contextmanagerdef 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. Let's take a closer look.
6. Using the generator to define the context manager
When discussing generators, it is said that we prefer generators to iterators that are implemented as classes because they are shorter and more convenient, states are saved locally rather than instances and variables. On the other hand, as described in the two-way communication section, the flow of data between the generator and its callers can be bidirectional. Includes exceptions that can be passed directly to the generator. We want to implement the context manager as a special generator function. In fact, the generator protocol is designed to support this use case.
@contextlib. Contextmanagerdef some_generator (<arguments>): <setup> try: yield <value > finally: <cleanup>
Contextlib.contextmanager decorates a generator and transforms it into a context manager. The generator must follow some of the rules that are enforced by the wrapped (wrapper) function--most importantly, it yields at least one time. The part before yield is executed from __enter__, the code block in the context manager executes when the generator stops at yield, and the rest is executed in __exit__. If an exception is thrown, the interpreter passes it through the __exit__ parameter to the wrapper function, and the wrapper function throws an exception at the yield statement. By using the generator, the context manager becomes shorter and more refined.
Let's use the generator to rewrite the closing example:
@contextlib. Contextmanagerdef closing (obj): try: yield obj finally: obj.close ()
Then rewrite the assert_raises as a generator:
@contextlib. Contextmanagerdef assert_raises (type): try: yield except type: return except Exception as value: raise Assertionerror (' wrong Exception type ') else: raise Assertionerror (' Exception Expected ')
Here we use adorners to convert the generated function into a context manager!