Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Context Managers

Yes! Our journey is complete, we are where I wanted to be. Context managers are one of my favorites, and a little underused, especially in user code, when they are really easy to both write and use (while decorators, for comparison, are really easy to use but a bit tricky to write). A context manager has a specific purpose.

A context manager is used for what I call “action at a distance”. It lets you schedule an action for later that is sure to always happen (unless you get a segfault, or exit a really nasty way). This is likely the most famous context manager:

with open(...) as f:
    txt = f.readlines()

When you enter the with block, __enter__ is called and the result is assigned to the as target, if there is one. Then when you leave the block, __exit__ is called. If you leave via an exception, __exit__ gets special arguments that let you even decide what to do based on that the exception is - or even handle the exception and continue. contextlib has several simple context managers, like redirect_stdout, redirect_stderr, and suppress:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    1 / 0
    print("This is never reached")

But the real star of contextlib is contextmanager, which is a decorator that makes writing context managers really easy. You use “yield” to break the before and after code. Let’s try one of my favorites, a timer context manager:

import time


@contextlib.contextmanager
def timer():
    old_time = time.monotonic()
    try:
        yield
    finally:
        new_time = time.monotonic()
        print(f"Time taken: {new_time - old_time} seconds")
with timer():
    print("Start")
    time.sleep(1.5)
    print("End")
Start
End
Time taken: 1.5002940650000056 seconds

As an extra bonus, contextmanager uses ContextDecorator, so the objects it makes can also be used as Decorators!

@timer()
def long_function():
    print("Start")
    time.sleep(1.5)
    print("End")


long_function()
Start
End
Time taken: 1.5002507939999958 seconds

Just a quick word on this: if you are coming from a language like JavaScript or Ruby, you might be thinking these look like blocks/Procs/lambdas. They are not; they are unscoped, and you cannot access the code inside the with block from the context manager (unlike a decorator, too). So you cannot create a “run this twice” context manager, for example. They are only for action-at-a-distance.

Pretty much everything in the contextlib module that does not have the word async in it is worth learning. contextlib.closing turns an object with a .close() into a context manager, and contextlib.ExitStack lets you nest context managers without eating up massive amounts of whitespace.