1. Context manager

This article is participating in Python Theme Month,See the activity link for details

Context managers allow you to allocate and release resources exactly as needed. The most widely used example of a context manager is the with statement. Suppose you have two related operations that you want to execute in pairs, with a block of code in between. The context manager can help you specifically do this. Such as:

with open('test_file', 'w') as s: s.write('hai! ')Copy the code

The code above opens the file, writes some data to it, and then closes it. If an error occurs while writing data to a file, it tries to close it. The above code is equivalent to:

file = open('test_file', 'w') try: file.write('hai! ') finally: file.close()Copy the code

When comparing this to the first example, we can see that using just with. The main advantage of using the with statement is that it ensures that our file is closed without paying attention to how the nested block exits.

A common use case for context managers is locking and unlocking resources and closing open files (as I’ve already shown you).

Let’s look at how to implement our own context manager. That should give us an accurate picture of what’s going on behind the scenes.

1.1 Implement the context manager as a class

An __enter__and __exit__ method is defined for the context manager. Let’s make our own file opening context manager and learn the basics.

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        self.file_obj.close()
Copy the code

We can use our new class in the with statement simply by defining __enter__ and __exit__ methods. Let’s try:

with File('text.txt', 'w') as s: s.write('hai! ')Copy the code

Our __exit__ method takes three arguments. They are required for every method __exit__ that is part of the context manager class. Let’s talk about what’s going on behind the scenes.

  1. thewithStatement store class__exit__Close method.
  2. It calls the class’s__enter__Methods.
  3. the__enter__Method opens the file and returns it.
  4. Open files passed tos.
  5. We use the.write().
  6. thewithThe statement calls the stored__exit__Close method.
  7. The call__exit__Method to close the file.

1.2 Handling Exceptions

We did not discuss the method’s type,value, and traceback arguments __exit__. Between steps 4 and 6, if an exception occurs, Python passes the type, value, and backtrace of the exception to the __exit__ method. It allows the __exit__ method to decide how to close the file and whether any further steps are required. In our case, we didn’t focus on them.

What if our file object throws an exception? We may be trying to access a method on a file object that it does not support. Such as:

with File('test.txt', 'w') as s: s.undefined_function('hai! ')Copy the code

Let’s list the steps the statement takes when with encounters an error:

  1. It passes the wrong type, value, and traceback to__exit__Methods.
  2. It allows the__exit__Method to handle exceptions.
  3. if__exit__To return to,TrueThe exception is handled properly.
  4. If the methodTrueDoes not return anything else (None)__exit__thewithStatement throws an exception.

In our example, the __exit__ method returns None (when no return statement is encountered, the method returns None). Therefore, the with statement throws an exception:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'
Copy the code

Let’s try handling exceptions in the __exit__ method:

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        print("Exception has been handled")
        self.file_obj.close()
        return True

with File('demo.txt', 'w') as opened_file:
    opened_file.undefined_function()

# Output: Exception has been handled
Copy the code

Our __exit__ method returns True, so the with statement does not throw an exception.

This is not the only way to implement a context manager. There is another way, which we will discuss in the next section.

1.3 Implement the context manager as a generator

We can also implement context managers using decorators and generators. For this purpose, Python has a contextlib module. We can use generator functions to implement context managers instead of classes. Let’s look at a basic example:

from contextlib import contextmanager

@contextmanager
def open_file(name):
    f = open(name, 'w')
    try:
        yield f
    finally:
        f.close()
Copy the code

Ok! This seems a more intuitive and simple way to implement the context manager. However, this approach requires some knowledge of generators, yields, and decorators. In this example, we did not catch any exceptions that might occur. It works in much the same way as the previous approach.

Let’s dissect this approach a little bit.

  1. Python metyieldThe keyword. Therefore, it creates a generator rather than a normal function.
  2. Due to decoration, the context manager takes the function name (open_file) is called as an argument.
  3. contextmanagerDecorations are returned by the package generatorGeneratorContextManagerObject.
  4. theGeneratorContextManagerAssigned toopen_fileFunction. Therefore, when we call this lateropen_fileFunction, we’re actually calling thatGeneratorContextManagerObject.

So now that we know all this, we can use the newly generated context manager like this:

with open_file('some_file') as f: f.write('hai! ')Copy the code