Context Managers

Context managers are objects that sandwich a block of code between a common enter code block and exit code block. They're powered by the with block:

with my_context_manager() as my_context:
    do_something_here(my_context)

Whenever you see a with block, a context manager is being used (the thing right after the with and before that as is the context manager: my_context_manager).

An example: file objects

Context managers are most commonly used when you have some "teardown" code that needs to be executed regardless of whether an exception has occurred before that point in your code.

For example file objects can act as a context manager to ensure the file is always closed after a particular with block exits.

This try-finally construct ensures our file is always closed, even if an error occurs while reading from it:

my_file = open('my_file.txt')
try:
    get_data = my_file.read()
finally:
    my_file.close()

Because file objects implement the context manager protocol, we can instead use the file object in a with block:

with open('my_file.txt') as my_file:
    get_data = my_file.read()

How do context managers work?

When you use a file object in a with block, Python doesn't call close automatically. Instead it calls __enter__ (when entering the with block) and __exit__ (when exiting the block).

You can think of this:

with open('my_file.txt') as my_file:
    get_data = my_file.read()

As sort of like this:

my_file = open('my_file.txt')
my_file.__enter__()
try:
    get_data = my_file.read()
finally:
    my_file.__exit__()

Technically it's actually more like this:

_context = open('my_file.txt')
my_file = _context..__enter__()
try:
    get_data = my_file.read()
except BaseException as _e:
    if not _context.__exit__(type(_e), _e, _e.__traceback__):
        raise
else:
    _context.__exit__()

But we don't often need to worry about all that complexity at once.

Related Resources