Now that we have an understanding of why we need context managers and the power of the with
statement, it is essential for us to know what’s happening under the hood to gain a much deeper understanding of the concept. The best way to see the internal workings of a context manager (such as the with
statement) is by creating our own!
One of the two approaches of creating context managers is referred to as the class-based approach. The class-based approach of writing context managers requires explicitly defining and implementing the following two methods inside of a class:
An
__enter__
method- The
__enter__
method allows for the setup of context managers. This method commonly takes care of opening resources (like files). This method also begins what is known as the runtime context - the period of time in which a script runs. In our previous examples, it was the time in which the code passed into thewith
statement code block was executed (basically everything under thewith
statement).
- The
An
__exit__
method- The
__exit__
ensures the breakdown of the context manager. This method commonly takes care of closing open resources that are no longer in use.
- The
To visualize these methods and the approach, let’s take a look at a custom class-based context manager below:
class ContextManager: def __init__(self): print('Initializing class...') def __enter__(self): print('Entering context...') def __exit__(self, *exc): print('Exiting context...')
Here, we defined a new class called ContextManager
(to be extra explicit) and implemented the required methods. By defining these two methods, we are implementing the context management protocol - a guideline for the required methods for a context manager. Don’t get too caught up in the arguments passed to each method, we will talk through them in the next exercises, but they are required to not experience an error.
Implementing the context management protocol allows us to immediately invoke the class using the with
statement as shown below:
with ContextManager() as cm: print('Code inside with statement')
Here we invoke the ContextManager
class with a with
statement.
After running the code, our output of this context manager would be:
Initializing class... Entering context... Code inside with statement Exiting context...
The above shows that our context manager class is executed in the following sequence:
__init__
method__enter__
method- The code in the
with
statement block __exit__
method
Let’s practice getting down the basics of writing a class-based context manager in addition to the execution flow before diving deeper into the __enter__
and __exit__
methods.
Instructions
Let’s create a context manager that will work with files filled with creative poems. While we won’t directly work with a file in this exercise, make sure to note the order of method execution in a context manager. Don’t worry, we’ll work with an actual file soon! For now, we are just going to get comfortable with the basics.
Create a class called PoemFiles
. For now, give it a single pass
statement so it won’t create an error when run.
Next, remove the pass
statement and create an __init__
method inside of the PoemFiles
class that prints 'Creating Poems!'
Let’s implement the __enter__
method. Have the method print 'Opening poem file'
.
Lastly, create an __exit__
method that prints 'Closing poem file'
.
Awesome! Now we have our very own context manager! Let’s see it in action by calling it with a with
statement.
Have the with
statement save the invoked class to a variable called manager
and have it print a famous line from the poet Emily Dickinson: 'Hope is the thing with feathers'
.