Context Managers

Python 3.12 · ✓ verified by execution on 2026-07-30

Have you ever opened a file using a with statement and marvelled at how it automatically closes itself when you’re done?

The magic behind the with statement is the Context Manager Protocol. A context manager is an object that defines a runtime context by handling the entry into and exit from a block of code.

Here is a visual representation of the context management lifecycle:

__enter__() with-block __exit__()
The Context Manager Protocol Lifecycle

Let’s trace how execution flows from __enter__ into the block, and finally to __exit__. Change value to see how exceptions trigger early exit!

python · visualize
class SuppressErrors:
    def __enter__(self):
        print("Entering context")
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"Exception suppressed: {exc_type.__name__}")
            return True # Suppress the exception!
        print("Exiting normally")

value = 0

with SuppressErrors():
    print(f"Working with value: {value}")
    if value == 0:
        raise ValueError("Cannot be zero!")
    print("Work complete")
print("Execution continues safely")

Building Class-Based Context Managers

To build a custom context manager using a class, you just need to implement __enter__ and __exit__.

python
class Timer:
    def __enter__(self):
        print('Timer started')
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Timer stopped')

with Timer() as t:
    print('Doing work')
Output
Timer started
Doing work
Timer stopped

Suppressing Exceptions

The __exit__ method takes three arguments related to an exception: the exception type, value, and traceback. If the context was exited normally, all three are None.

If an exception occurs and __exit__ returns a truthy value, Python suppresses the exception!

python
class SuppressErrors:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f'Suppressed {exc_type.__name__}')
            return True

with SuppressErrors():
    raise ValueError('Oops')
print('Execution continues')
Output
Suppressed ValueError
Execution continues

The @contextmanager Decorator

Writing classes just to get __enter__ and __exit__ can be boilerplate-heavy. Python’s contextlib module provides the @contextmanager decorator, which turns a generator function into a full context manager.

Everything before the yield statement acts as __enter__. The yield itself passes the bound value to the as clause. Everything after the yield acts as __exit__.

python
from contextlib import contextmanager

@contextmanager
def tag(name):
    print(f'<{name}>')
    yield
    print(f'</{name}>')

with tag('h1'):
    print('Hello World')
Output
<h1>
Hello World
</h1>

Exception Propagation in Generators

When using @contextmanager, any unhandled exception in the with block is literally re-raised inside your generator at the line where yield occurred. To handle cleanup safely, you must wrap your yield in a try/finally block!

python
from contextlib import contextmanager

@contextmanager
def safe_open():
    print('Opening')
    try:
        yield
    except ValueError:
        print('Caught ValueError')
    finally:
        print('Closing')

with safe_open():
    print('Working')
    raise ValueError('Bad value')
Output
Opening
Working
Caught ValueError
Closing

Managing Dynamic Contexts: ExitStack

Sometimes you don’t know ahead of time how many context managers you need to open (e.g., dynamically opening multiple files). The ExitStack is designed exactly for this: it allows you to dynamically register context managers, and it ensures all of them are cleaned up properly when the stack is exited.

python
from contextlib import ExitStack

with ExitStack() as stack:
    print('Managing multiple contexts dynamically')
    # stack.enter_context(open('file.txt')) would go here
    print('Done')
Output
Managing multiple contexts dynamically
Done

The Standard Library Example

The most prominent example of a context manager is file handling. File objects natively implement this protocol.

python
import tempfile
import os

with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(b'Hello')
    name = f.name

with open(name, 'r') as f:
    print(f.read())

os.remove(name)
Output
Hello

Check yourself

In `with open(path) as f:`, what exactly gets bound to f?

Reveal answer

Whatever __enter__ returns — The as-name receives the return value of __enter__. That is often self, but it does not have to be.

An exception is raised inside a with block. Does __exit__ still run?

Reveal answer

Yes — on success, on exception, and on early return — Guaranteed cleanup is the entire point of a context manager: __exit__ runs on every exit path.

In an @contextmanager generator, where does an exception from the with body surface?

Reveal answer

At the yield statement, so teardown needs try/finally around it — The exception is thrown in at the yield. Without try/except/finally around it, your teardown code never runs.

What does returning a falsey value from __exit__ do?

Reveal answer

Lets the original exception propagate normally — Return truthy to suppress the exception; falsey (including None) lets it propagate — which is what you almost always want.

Challenges

Challenge 1 +100 XP

Create a class `ExecutionTimer` that implements the context manager protocol. It should record `self.start = time.time()` in `__enter__` and `self.end = time.time()` in `__exit__`. It should also calculate `self.duration` in `__exit__`.

python
  • Test 1 — expects "True\n"
Need a hint? (−25% XP)

Remember to return `self` from `__enter__` so the object can be accessed if used with `as`.

Show solution (0 XP)
import time

class ExecutionTimer:
    def __enter__(self):
        self.start = time.time()
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.time()
        self.duration = self.end - self.start

with ExecutionTimer() as timer:
    time.sleep(0.01)
print(timer.duration > 0)

Challenge 2 +100 XP

Use `@contextmanager` to write a context manager called `temp_setattr` that temporarily sets an attribute on an object, and restores the original value when exiting.

python
  • Test 1 — expects "2\n1\n"
Need a hint? (−25% XP)

Use a try/finally block around yield to guarantee the attribute is restored even if an exception occurs.

Show solution (0 XP)
from contextlib import contextmanager

@contextmanager
def temp_setattr(obj, attr, value):
    original = getattr(obj, attr)
    setattr(obj, attr, value)
    try:
        yield
    finally:
        setattr(obj, attr, original)

class Dummy:
    val = 1
d = Dummy()
with temp_setattr(d, "val", 2):
    print(d.val)
print(d.val)