As a class

Any object that implements the context manager protocol can be used as a context manager. This protocol consists of two special methods:

  • __enter__(self): This allows you to define what should happen before executing the code that is wrapped with context manager and returns context variable
  • __exit__(self, exc_type, exc_value, traceback): This allows you to perform additional cleanup operations after executing the code wrapped with context manager, and captures all exceptions that were raised in the process

In short, the execution of the with statement proceeds as follows:

  1. The __enter__ method is invoked. Any return value is bound to target the specified as clause.
  2. The inner block of code is executed.
  3. The __exit__ method is invoked.

__exit__ receives three arguments that are filled when an error occurs within the code block. If no error occurs, all three arguments are set to None. When an error occurs, the __exit__() method should not re-raise it, as this is the responsibility of the caller. It can prevent the exception being raised, though, by returning True. This is provided to allow for some specific use cases, such as the contextmanager decorator, which we will see in the next section. But, for most use cases, the right behavior for this method is to do some cleanup, as would be done by the finally clause. Usually, no matter what happens in the block, it does not return anything.

The following is an example of a dummy context manager that implements this protocol to better illustrate how it works:

class ContextIllustration: 
    def __enter__(self): 
        print('entering context') 
 
    def __exit__(self, exc_type, exc_value, traceback): 
        print('leaving context') 
 
        if exc_type is None: 
            print('with no error') 
        else: 
            print(f'with an error ({exc_value})') 

When run without exceptions raised, the output is as follows (the previous snippet is stored in the context_illustration module):

>>> from context_illustration import ContextIllustration
>>> with ContextIllustration():
... print("inside") ... entering context inside leaving context with no error

When the exception is raised, the output is as follows:

>>> from context_illustration import ContextIllustration
>>> with ContextIllustration(): ... raise RuntimeError("raised within 'with'") ... entering context leaving context with an error (raised within 'with') Traceback (most recent call last): File "<input>", line 2, in <module> RuntimeError: raised within 'with'