Context Managers

Indices and tables

Introduction

Context managers are used to create the environment (context) required by a section of code, and restore the environment to its original state after the section of code terminates.

There are two methods defined by a context manager:

  • __enter__() - Executed prior to a section of code executing. Returns an object, related to the context that was created.
  • __exit__() - Executed after the section of code is done executing. Guaranteed to run regardless of why the code finished executing (ie. normally or due to an exception), therefore, it can sometimes replace the finally block in a try statement.

Python supplies some built-in context managers:

  • The open() function returns a context manager, which ensures any file opened is always closed.
  • SQL Lite connection objects, which automatically commit or roll-back a transaction if an exception occurs.
  • The Lock class of the multiprocessing package, which ensures the lock is released if an exception occurs.

Context manages can be used on their own by invoking their methods. However, it is more common to use the with statement to do this.

With Statement

The with statement interfaces with the context manager(s) to automatically:

  • Setup the context required prior to the with_suite executing
  • Teardown the context created after with_suite finishes executing (either naturally or due to an exception).

The with statement can be used as follows:

with `expression0` [as `target0`] (, `expressionN` [as `targetN`])* :
    `with_suite`
  • The expression is evaluated and expected to return a context manager object.

  • The __enter__() method of the context manager object is executed and returns a value related to the context.

  • If target is present, the value returned by __enter__() is assigned to it.

  • The with_suite is executed.

  • The __exit__() method of the context manager object is executed.

    • If an exception occurs in the with_suite, the __exit__() function is supplied with it. The __exit__() method can decide to handle the exception, ignore it, or let it through un-handled.
  • You can nest context managers in a with statement with additional expression and target sections.

Let’s use a code example to demonstrate the operation.

We define a custom context manager that will be vocal about what is executing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MyContextMgr():

    def __enter__(self):
        print("Entering context manager")
        return "Hello World"

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            print("Found an exception. Ignoring")
        print("Exiting context manager")
        return True

Now, the code that uses the context manager in a with statement:

1
2
3
4
with MyContextMgr() as val:
    print("Executing with_suite")
    print("Context manager val: {}".format(val))
    print("Done with_suite")

Running the code produces the following output:

Entering context manager
Executing with_suite
Context manager val: Hello World
Done with_suite
Exiting context manager

If an exception was thrown in the with_suite, as in:

1
2
3
4
5
with MyContextMgr() as val:
    print("Executing with_suite")
    raise Exception
    print("Context manager val: {}".format(val))
    print("Done with_suite")

The __exit__() method is still invoked even if though the with_suite was terminated early:

Entering context manager
Executing with_suite
Found an exception. Ignoring
Exiting context manager

Going back to the idea of context managers created by open(), they work like the following:

with open("foo.txt") as fh:
    for line in fh:
        print(line)

In this example, the foo.txt file is opened and the file handle that points to it is passed into the with_suite as fh. Regardless of how the with_suite terminates, the file will be closed by the context manager.

contextlib

The contextlib package supplies some handy decorators and context manager classes. For example:

suppress(*exceptions)

Return a context manager that suppresses any of the specified exceptions if they occur in the body of a with statement and then resumes execution with the first statement following the end of the with statement.

An example of its usage is below:

import os
from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('foo.txt')

In the above case, regardless of what happens, no FileNotFoundError exception will make it outside the with statement.

There are other context managers in that package that you should explore and considering using where possible.

Try it!

Re-write the following try block to use a with statement.

try:
    fh = open("results.txt", mode='w')
    fh.writeline("Success!")
finally:
    if fh is not None:
        fh.close()