Context Managers in Python
Tags: python, contextmanagers, decorators, generators
Published: 2019-07-20 17:59:00

---
title: Context Managers in Python
published: true
description: What are Context Managers? When to use them? How to write your own? 
tags: Python, Context Managers, Decorators, Generators 
---

# 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. 


```Python
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: 


```Python
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.
 3. The __enter__ method opens the file and returns it.
 4. The opened file handle is passed to *f*.
 5. We write to the file using *.write()*.
 6. The with statement calls the __exit__ method.
 7. 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*.

```Python
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.

```Python
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.

```Python
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:
```Bash
>>> 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 :smile: