Python Context Manager

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

An execution context is useful when the code uses resources that need closing, even if the code follows an unexpected execution path, like when an exception is raised.

An execution context is equivalent with the following construct:

r = Resource()
r.open()
try:
  #
  # use resource, while anywhere in this code block exception might be thrown
  #
finally:
  r.close()

The equivalent semantics using a context manager can be achieved with this syntax:

with <create-context> as ctx:
  #
  # use the context
  #

#
# at the exit from the indented block, the context is automatically cleaned up
#

How it Works

When the interpreter encounters the with statement, it executes whatever follows with, expecting that the result of the execution is a context manager object instance: an object that implements the context manager protocol.

The interpreter continues by invoking the __enter__() on the context manager instance. The __enter__() method may optionally return an object instance, which is assigned to the variable name following the as reserved keyword. The instance is accessible from the indented block that follows with, and its scope is the same as the scope of the with statement. That means the instance can be accessed both inside and after the with statement.

After the code in the block is executing, upon exiting the indented block, the interpreter executes the context manager instance's __exit__() method, which is supposed to cleanup the context, close and release resources used within the context.

class SomeTestingContextType:
    def __str__(self):
        return f'SomeTestingContextType[{self.__hash__()}]'
    pass


class TestingContextManager:
    #
    # __enter__() is part of the context manager protocol
    #
    def __enter__(self):
        print('entering __enter__()')
        #
        # this is the instance that will be assigned to the
        # variable which follows the reserved keyword 'as'
        #
        return SomeTestingContextType()

    #
    # __exit__() is part of the context manager protocol
    #
    def __exit__(self, ex_type, ex_value, ex_traceback):
        print('entering __exit__()')
        print('exiting __exit__()')


def testing_context():
    return TestingContextManager()


with testing_context() as i:
    print('    in the execution context')
    print(f'    i = {i}')
print('outside the execution context')

The code shown above displays:

entering __enter__()
    in the execution context
    i = SomeTestingContextType[274296980]
entering __exit__()
exiting __exit__()
outside the execution context

If an exception is raised anywhere inside the execution context block, the context manager's __exit__() is still executed. The following code:

with testing_context() as i:
    print('    in the execution context')
    print(f'    i = {i}')
    raise ValueError('some error')
print('outside the execution context')

displays the following:

Traceback (most recent call last):
  File "/Users/ovidiu/src/github.com/playground/pyhton/experiments2/main.py", line 34, in <module>
    raise ValueError('some error')
ValueError: some error
entering __enter__()
    in the execution context
    i = SomeTestingContextType[281816212]
entering __exit__()
exiting __exit__()

Note that "outside the execution context" is not displayed anymore.

Context Manager

The context manager is an instance of a class that implements the context manager protocol. The context manager defines and manages a runtime context.

Context Manager Protocol