Python Snippet
Python Context Managers
The with statement is more powerful than you think. Learn to build your own.
Last updated: October 2026
Quick answer: A context manager defines setup and cleanup around a block of code. Use the with statement to run it — cleanup runs even if an exception occurs. Build one by implementing __enter__ and __exit__, or use the @contextmanager decorator on a generator function for a shorter version.
🎬 Interactive Live Demo
Click through the tabs to see different context manager patterns and their simulated execution traces.
💡 Switch between tabs to see how setup/cleanup flows through each pattern.
The with Statement — Why It Matters
Without with, resource cleanup is error-prone:
f = open("data.txt", "r")
try:
content = f.read()
print(content)
finally:
f.close()
With with, cleanup is guaranteed and the code is cleaner:
with open("data.txt", "r") as f:
content = f.read()
print(content)
The file closes even if f.read() raises an exception. That guarantee is the entire point of context managers.
Class-Based Context Manager
Implement two methods: __enter__ (setup) and __exit__ (cleanup):
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.time() - self.start
print(f"Elapsed: {elapsed:.3f}s")
# Returning False (or None) propagates any exception
return False
with Timer():
total = sum(range(1_000_000))
# Elapsed: 0.015s
Key details:
__enter__returns whatever theasvariable gets.__exit__receives exception info if one occurs.- Returning
Truefrom__exit__suppresses the exception. - Returning
False(or nothing) propagates it.
Generator-Based Context Manager
The @contextmanager decorator turns a generator into a context manager. Cleaner for simple cases:
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
yield
print(f"</{name}>")
with tag("p"):
print("Hello")
# <p>
# Hello
# </p>
The pattern: Code before yield is setup. Code after yield is cleanup. Use try/finally if the block can raise:
@contextmanager
def transaction(db):
db.begin()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
Handling Exceptions in __exit__
When an exception occurs inside the with block, Python passes the details to __exit__:
class Suppressor:
def __init__(self, *exceptions):
self.exceptions = exceptions
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
return False
# Suppress only the listed exception types
return issubclass(exc_type, self.exceptions)
with Suppressor(ZeroDivisionError):
result = 1 / 0 # Silently ignored
print("Continues here")
This is exactly how contextlib.suppress works internally.
contextlib — The Built-In Helpers
Python ships four context manager helpers you should know:
from contextlib import suppress, closing, ExitStack, redirect_stdout
# 1. suppress — ignore specific exceptions
with suppress(FileNotFoundError):
open("missing.txt")
# 2. closing — auto-close any object with .close()
from urllib.request import urlopen
with closing(urlopen("https://example.com")) as page:
data = page.read()
# 3. ExitStack — dynamic number of managers
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in filenames]
# All files closed on exit, in reverse order
# 4. redirect_stdout — capture prints
import io
buffer = io.StringIO()
with redirect_stdout(buffer):
print("Captured!")
print(buffer.getvalue()) # "Captured!\n"
Real-World Use Case: Database Transactions
from contextlib import contextmanager
@contextmanager
def transaction(connection):
cursor = connection.cursor()
try:
yield cursor
connection.commit()
except Exception:
connection.rollback()
raise
finally:
cursor.close()
# Usage
with transaction(db) as cursor:
cursor.execute("UPDATE users SET name = ? WHERE id = ?", ("Alice", 1))
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE user_id = ?", (1,))
# Commits automatically on success, rolls back on any exception
This pattern appears in nearly every production Python codebase. It's the reason with exists.
🎯 Class vs @contextmanager — When to Use Each
| Aspect | Class-Based | @contextmanager |
|---|---|---|
| Boilerplate | More | Less |
| Reusability | Better (can subclass) | Limited |
| Complex state | Handles well | Possible but awkward |
| Best for | Reusable utilities | One-off setup/cleanup |
⚠️ Common Mistakes
- Forgetting to return from
__enter__. Theasvariable will beNone. Always returnselfor the value the user needs. - Accidentally suppressing exceptions. Returning
Truefrom__exit__hides the exception silently. ReturnFalseunless suppression is intentional. - Using
@contextmanagerwithouttry/finally. If the block raises, code afteryieldnever runs — cleanup leaks. Wrap the yield intry/finally. - Forgetting the
yieldin a generator-based manager. Without ayield, the function returns a generator, not a context manager. - Assuming
__exit__receives every exception type. It doesn't catch exceptions raised by__enter__— those propagate directly. - Nesting without order awareness. Multiple managers exit in reverse order (last-in, first-out), like decorators.
❓ Frequently Asked Questions
What is a context manager in Python?
A context manager is an object that defines setup and cleanup behavior for a block of code. It is used with the with statement, which guarantees cleanup runs even if an exception is raised.
What does the with statement do?
The with statement calls __enter__ on the object to set up resources, runs the block, then calls __exit__ to clean up. Even if the block raises an exception, __exit__ is called.
What is the difference between class-based and generator-based context managers?
Class-based managers implement __enter__ and __exit__ methods and offer more control. Generator-based managers use the @contextmanager decorator and are more concise. Use @contextmanager for simple cases.
What does contextlib.suppress do?
contextlib.suppress is a context manager that silently ignores specific exceptions. It replaces try/except/pass blocks and makes intent explicit.
Can I have multiple context managers in one with statement?
Yes. Use with open('a') as a, open('b') as b: to chain multiple context managers. They are entered left-to-right and exited right-to-left, similar to how nested decorators work.