Context Managers in Python
Context Managers in Python help the users to manage the resources efficiently in a program i.e opening or closing a resource or locking or unlocking a resource. Context Managers come handy when we have to manage resource before and after an event.
The most common context manager that every python programmer uses very frequently for managing files, is a with as statement.
with open('MyContextManager.txt') as f:
f.write('Using Context Manager is FUN !')
The above code snippet has mainly two advantages:
- Helps to close resources automatically and effectively. This is a small code block, so it could be observed easily that the file was opened and closed properly but what would when the scope of the function increases? This is where context managers really come into the picture.
- Makes the code readable and complex logic simple. The above code can also be written as:
file = open('MyContextManager.txt','W')
try:
file.write('Not using a Context Manager.')
finally:
file.close()
Here we manage the opening and closing of a file manually with a try-finally statement.
Python's standard library comes with a module, contextlib. This contains utilities for working with context managers and the with statement.
Writing Your Own Context Manager
So why would someone want to write their own Context Managers?
Because Context Managers are best at managing resources before and after an event; thus one doesn't have to worry about the allocation/de-allocation or Locking/Unlocking or Opening/Closing of events.
Also, they make the code simple and more readable.
Writing your own context manager can be done in two ways; either create your own class or use the Contextlib module to create a Context Manager decorator.
Let's first look at how we can create a simple Context Manager class. A Context Manager class consists of two main methods enter and exit. If you're familiar with testing, you can compare these two methods with the setup and teardown.
Just like every class in Python, the init method is optional. But in the case of Context Managers, we use init only if we're using a with as statement. init has to be passed the name which you want to associate with as in the with as statement.
Now let's take a look at a simple Game of Thrones inspired ContextManager which creates a dict of the house symbols.
class ThronesContextManager:
def __init__(self, HouseSymbol):
self.HouseSymbol = HouseSymbol
def __enter__(self):
print("Enter: {}".format(self.HouseSymbol)")
return self.HouseSymbol
def __exit__(self, *exc):
print("Exit: {}".format(self.HouseSymbol))
with ThronesContextManager({"House Stark": "Wolf"}) as HouseSymbol:
HouseSymbol["Targaryen"] = "Three Headed Dragon"
"""
---Output---
Enter: {'House Stark': 'Wolf'}
Exit: {'House Stark': 'Wolf', 'Targaryen': 'Three Headed Dragon'}
"""
- The init method takes in the dict associated with the as, similar to as what is done in the with-as statement. It creates an instance of the class and assigns it to the dict. Much similar to any normal Python Class.
- The enter method is called by the with and is passed the dict. It returns the value which is associated with the dict(HouseSymbol).
- The exit takes in the exception(*exc), these are of mainly three types exc: exception, exctype: exception type and exctb: exception_traceback.
- If for some reason you want the program to ignore the exception you can also return True to just ignore the exception.
Now taking a look at the above code example we can say that any Context Manager has two methods an enter method and an exit method.
Before moving forward to contextmanager decorator let's break down the code snippet we saw in the starting of the post and see how it works behind the hood.
Since we know how context managers work it won't be difficult to the observe what's happening when we call with as statement while opening a file.
with open('MyContextManager.txt') as f:
f.write('Using Context Manager is FUN !')
- With calls the enter method of the File class.
- The enter method opens the file and returns it.
- The opened file handle is passed to f.
- We write to the file using .write().
- The with statement calls the exit method.
- The exit checks for exceptions, if no exception is found it closes the file.
The easier way to write a context manager is by using the Contextlib module and creating a context manager decorator.
The good thing about using the @contextmanager is that it builds the enter and exit method for you automatically, thus we can transform a generator function into a contextmanager decorator.
Let's re-write the ThronesContextManager again but with a @ThronesContextManager.
from contextlib import contextmanager
@contextmanager
def ThronesContextManager(data):
print("Enter: {}".format(data))
yield data
print("Exit: {}".format(data))
with ThronesContextManager({"House Stark": "Wolf"}) as HouseSymbol:
HouseSymbol["House Targaryen"] = "Three Headed Dragon"
"""
---Output---
Enter: {'House Stark': 'Wolf'}
Exit: {'House Stark': 'Wolf', 'House Targaryen': 'Three Headed Dragon'}
"""
PyRandom
Here are some interesting things I found about Contextmanagers. I came across these while researching for this blog post and hence the that's the reason I am adding this to the section PyRandom. I would keep updating this section as I learn more about Context Managers.
- Context Managers do not create a separate new scope in the program i.e variables defined inside the withas block will be available after the block is executed.
with open('MyContextManager.txt') as f:
# Variable defined inside the Context Manager
VariableName = f.read()
print(VariableName)
- When using multiple ContextManager in a withas statement the flow of enter and exit statement becomes LIFO(Last In First Out) i.e the enter method that is called last will have it's exit method called first.
import contextlib
@contextlib.contextmanager
def make_context(name):
print ('entering:', name)
yield name
print ('exiting :', name)
with make_context('A') as A, make_context('B') as B, make_context('C') as C:
print ('inside with statement:', A, B, C)
"""
---OUTPUT---
entering: A
entering: B
entering: C
inside with statement: A B C
exiting : C
exiting : B
exiting : A
"""
What now ?
Since we covered all the basic stuff on Context Managers, we can start digging deeper and learn to use Context Managers in a more realistic scenarios. So here are a few things that I would like you to read more about:
- Learn how to handle exceptions in/with Context Managers.
- Try to find out real project use cases where using a Context Manager would be best suited.
- Find out the role of init and enter in the Context Manager.
Still can't get enough ?
The reason behind the blog is that I recently picked a Python problem which goes something like this
Write a Context Manager named Suppress which suppresses the exception of a given type/types i.e if the given exception type is raised, that exception should be caught and muted in a sense.
Code Example:
>>> x = 0
>>> with suppress(ValueError):
... x = int('hello')
...
>>> x
0
>>> with suppress(ValueError, TypeError):
... x = int(None)
...
>>> x
0
Since you read this far I am assuming you are also just starting to learn about this topic. Let's put it all that we have learned so far to a test and get some hands-on experience of writing your Context Manager. Try to solve this problem.
I am still solving the problem and once it's done I would link my solution here.
Happy Coding !