DEV Community

Pradhvan Bisht
Pradhvan Bisht

Posted on

Context Managers in Python

Context Managers in Python

Context Managers in Python helps the user 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 advantage:

  • Helps to close resources automatically and effectively. This might be a small code block, so it might a bit obvious that the file was open and closed properly but what if when the scope of the function increases? This is why 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 there own Context Managers?

Because, Context Managers are the best at managing resources before and after an event. Thus you don't have to worry about the allocation/de-allocation or Locking/Unlocking or Opening/Closing.

Also, they make the code simple and more readable.

Writing your 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 in 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.


```PythonJust like every classJust like every class in Python, the init method is optional only if you're using a with * statement. If you're using *with as statement it has to be passed the name which you want to associate with as in the with as statement. in Python, the init method is optional only if you're using a with * statement. If you're using *with as statement it has to be passed the name which you want to associate with as in the with as statement.
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* 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, exc_type: exception type and exc_tb: 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 is one which as two methods an *enter* method and an *exit* method. 

Before moving forward to *contextmanager decorator* let's break down the code snippit 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 observer what's happening when we call *with as* statement while opening a file.




```Python
with open('MyContextManager.txt') as f:
    f.write('Using Context Manager is FUN !')
  1. With calls the enter method of the File class.
  2. The enter method opens the file and returns it.
  3. The opened file handle is passed to f.
  4. We write to the file using .write().
  5. The with statement calls the exit method.
  6. 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 so 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["Targaryen"] = "Three Headed Dragon"

"""
---Output---
Enter: {'House Stark': 'Wolf'}
Exit: {'House Stark': 'Wolf', 'Targaryen': 'Three Headed Dragon'}
"""

PyRandom

Here are some things interesting things I found about Contextmanagers. These were found when I was researching for this blog post thus I am adding this to the section PyRandom. I would be constanly updating this section as I learn more about Contex 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 the 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 real-life use case. So here are some things that I would like you to read more about it.

  • 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 to the put to the test what you have learned so far 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 😄

Top comments (0)