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, by throwing an exception.

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-and-return-context-manager> as some_instance:
  #
  # use the context, access 'some_instance'
  #

#
# 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. Note that the instance references the object returned by __enter__(), it does not references the context manager instance.

After the code in the block has successfully executed, or if an exception is thrown from that block, 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. The __exit__() method returns a boolean value. If __exit__() returns True, the Python interpreter will make the exceptions silent.

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')
print(f'i = {i}')

The code shown above displays:

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

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

The context manager protocol consists of the __enter__() and __exit__() methods, explained above.

Usage

The pattern is useful in the following situations:

1. Open/Close: if you want to open and close a resource automatically, for example opening and closing a socket.

2. Lock/Release: context manager can help with managing locks more effectively. It allows you to acquire a lock and release it automatically.

3. Start/Stop: Context managers also help you to work with a scenario that requires the start and stop phases. For example, you can use a context manager to start a timer and stop it automatically.