Skip to content
On this page

contextlib

In Python, it's crucial to properly close resources like files after use. One traditional way to ensure this is by using try...finally:

python
try:
    f = open('/path/to/file', 'r')
    f.read()
finally:
    if f:
        f.close()

However, writing try...finally can be cumbersome. Python's with statement allows us to easily manage resources without worrying about forgetting to close them, simplifying the code to:

python
with open('/path/to/file', 'r') as f:
    f.read()

The with statement can be used with any object that correctly implements context management. Context management is achieved through the __enter__ and __exit__ methods. For example, the following class implements these methods:

python
class Query(object):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Begin')
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')
    
    def query(self):
        print('Query info about %s...' % self.name)

Now, we can use our custom resource object with the with statement:

python
with Query('Bob') as q:
    q.query()

@contextmanager

Implementing __enter__ and __exit__ can still be quite verbose, so Python's standard library contextlib provides a simpler way to achieve this with the @contextmanager decorator. The previous code can be rewritten as follows:

python
from contextlib import contextmanager

class Query(object):
    def __init__(self, name):
        self.name = name

    def query(self):
        print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')

The @contextmanager decorator accepts a generator function, yielding the variable to be used in the with ... as var statement. The with statement will then function correctly:

python
with create_query('Bob') as q:
    q.query()

Often, we want to automatically execute specific code before and after a block of code, which can also be implemented with @contextmanager. For example:

python
@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

with tag("h1"):
    print("hello")
    print("world")

The output of the above code will be:

<h1>
hello
world
</h1>

The execution order is as follows:

  1. The with statement first executes the code before yield, printing <h1>.
  2. The yield call executes all statements inside the with block, printing hello and world.
  3. Finally, the code after yield executes, printing </h1>.

Thus, @contextmanager simplifies context management by allowing us to write generators.

@closing

If an object does not implement context management, it cannot be used with the with statement. In such cases, we can use closing() to convert that object into a context object. For instance, using urlopen() with a with statement:

python
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

The closing function is also a generator decorated with @contextmanager, which is quite simple to write:

python
@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

Its purpose is to turn any object into a context object that supports the with statement.

The @contextlib module includes other decorators that help us write more concise code as well.

contextlib has loaded