Python Context Managers in Depth

python context managers

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!

Resources

Get new content delivered to your mailbox:

leave a comment