Python is a particularly clean and sugary language, thanks to its many convenience features. In this post I will go into context managers in Python, how to use them, where to find them, and how to write your own ones.
Why do we need Context Managers?
Context managers are most often used when we are talking about resources. For example, reading/writing from files. Examine this simple snippet:
f = open('log.txt', 'w')
f.write('hello world')
f.close()
All this code does is open the log.txt
for writing and writes hello world
in it. Simple enough. But suppose for a minute, that the code on line 2 throws an exception for no reason whatsoever. In this case, file is still opened, but never closed, as the interpreter never gets to line 3. This is not a problem with a throwaway script, but can become a headache if you are to develop anything serious on Python.
How can we fix this? The try
/finally
construction comes into mind:
f = open('log.txt', 'w')
try:
f.write('hello world')
finally:
f.close()
This works, since finally
will be executed even if an exception is thrown and file descriptor will be discarded. However, this does not look very Pythonish…
This is where the with
keyword comes into play. It was developed for this specific purpose: to make resource management readable. This snippet is functionally equal to the example above:
with open('log.txt', 'w') as f:
f.write('hello world')
Looks much cleaner, won’t you agree?
How do Context Managers work?
The logic behind context managers is actually pretty easy. Let’s explore it by writing our own context manager. For an object to be recognized as a context manager, it must implement 2 methods: __enter__
and __exit__
:
If you try running this example, you will get the following output:
Entered into context manager!
Inside context manager!
Exiting context manager!
Now let’s try throwing an exception inside with
:
with TestContextManager():
raise Exception()
If you try running this version, note that the __exit__
method is still called, much like the finally
clause in the try
block. This can be used to clean up and release any resources used by this context manager.
contextlib
module
All of this would not be particularly useful if not for the contextlib
module. This module is part of the standard library and provides some common constructs to make your life easier.
Most notable addition is the @contextmanager
decorator. It lets you convert any generator function into a context manager with no additional code at all! Here is an example:
While this still requires the use of try
/finally
, this lets you abstract this away once and forget about it, while also keeping your code compatible with the rest of the codebase which might not understand context managers as well as you now do.
Another useful feature of contextlib
is the AbstractContextManager
class (new in Python 3.6). This is an Abstract Base Class which implements the __enter__
and __exit__
functions. Use it in your custom context managers to make sure the types are compatible.
Closing notes
Thank you for reading, I hope you enjoyed this article. In my next post, I will talk about Asynchronous Context Managers. Stay tuned!