Hi! I am currently looking for some freelance projects. If you have a project idea in mind please feel free to send me an email or check out my personal website. If you want to get to know about my journey, you can read this article: How I got into programming

Context managers allow you to allocate and release resources precisely
when you want to. The most widely used example of context managers is
the with statement. Suppose you have two related operations which
you’d like to execute as a pair, with a block of code in between.
Context managers allow you to do specifically that. For example:

withopen('some_file','w')asopened_file:opened_file.write('Hola!')

The above code opens the file, writes some data to it and then closes
it. If an error occurs while writing the data to the file, it tries to
close it. The above code is equivalent to:

file=open('some_file','w')try:file.write('Hola!')finally:file.close()

While comparing it to the first example we can see that a lot of
boilerplate code is eliminated just by using with. The main
advantage of using a with statement is that it makes sure our file
is closed without paying attention to how the nested block exits.

A common use case of context managers is locking and unlocking resources
and closing opened files (as I have already showed you).

Let’s see how we can implement our own Context Manager. This would allow
us to understand exactly what’s going on behind the scenes.

We did not talk about the type, value and traceback
arguments of the __exit__ method. Between the 4th and 6th step, if
an exception occurs, Python passes the type, value and traceback of the
exception to the __exit__ method. It allows the __exit__ method
to decide how to close the file and if any further steps are required.
In our case we are not paying any attention to them.

What if our file object raises an exception? We might be trying to
access a method on the file object which it does not supports. For
instance:

classFile(object):def__init__(self,file_name,method):self.file_obj=open(file_name,method)def__enter__(self):returnself.file_objdef__exit__(self,type,value,traceback):print("Exception has been handled")self.file_obj.close()returnTruewithFile('demo.txt','w')asopened_file:opened_file.undefined_function()# Output: Exception has been handled

Our __exit__ method returned True, therefore no exception was raised
by the with statement.

This is not the only way to implement context managers. There is another
way and we will be looking at it in this next section.

We can also implement Context Managers using decorators and generators.
Python has a contextlib module for this very purpose. Instead of a
class, we can implement a Context Manager using a generator function.
Let’s see a basic, useless example:

Okay! This way of implementing Context Managers appears to be more
intuitive and easy. However, this method requires some knowledge about
generators, yield, and decorators. In this example we have not caught any
exceptions which might occur. It works in mostly the same way as the
previous method.

Let’s dissect this method a little.

Python encounters the yield keyword. Due to this it creates a
generator instead of a normal function.

Due to the decoration, contextmanager is called with the function
name (open_file) as it’s argument.

The contextmanager function returns the generator wrapped by the
GeneratorContextManager object.

The GeneratorContextManager is assigned to the open_file
function. Therefore, when we later call open_file function, we
are actually calling the GeneratorContextManager object.

So now that we know all this, we can use the newly generated Context
Manager like this: