contextlib
Reviewed & published by Brayan K
The contextlib module gives you tools for working with the with-statement — most notably @contextmanager, which turns a simple generator into a reusable setup-and-cleanup context manager.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll also meet suppress for ignoring specific errors, redirect_stdout for capturing output, and ExitStack for managing a dynamic number of resources at once.
What You'll Learn in This Lesson
- Writing context managers with @contextmanager and yield
- Ignoring specific errors with suppress()
- Capturing print output with redirect_stdout
- Managing dynamic resources with ExitStack
- Wrapping close()-able objects with closing()
- Why setup runs before yield and cleanup after
1 @contextmanager: Setup, yield, Cleanup
The @contextmanager decorator turns a generator into a context manager. Code before yield is the setup (runs on entering the with); code after yield is the cleanup (runs on exit, even if an error occurs):
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>") # setup
yield # body of the with-block runs here
print(f"</{name}>") # cleanup
with tag("p"):
print("Hello!")
# <p>
# Hello!
# </p>from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
yield
print(f"</{name}>")
with tag("p"):
print("Hello!")
# ✅ Expected output:
# <p>
# Hello!
# </p>2 suppress() and redirect_stdout()
suppress(SomeError) swallows a specific exception so the program keeps going — a clean replacement for try/except: pass. redirect_stdout(buffer) captures everything print() writes into a string buffer instead of the screen:
from contextlib import suppress
# Cleanly ignore a missing file instead of try/except
with suppress(FileNotFoundError):
open("does_not_exist_12345.txt")
print("No crash - the error was suppressed")import io
from contextlib import redirect_stdout
buffer = io.StringIO()
with redirect_stdout(buffer):
print("captured line 1")
print("captured line 2")
text = buffer.getvalue()
print("Captured", len(text.splitlines()), "lines")
print(repr(text))
# Captured 2 lines
# 'captured line 1\ncaptured line 2\n'import io
from contextlib import suppress, redirect_stdout
# 1. suppress a specific error
with suppress(FileNotFoundError):
open("does_not_exist_12345.txt")
print("No crash - the error was suppressed")
# 2. capture printed output into a string
buffer = io.StringIO()
with redirect_stdout(buffer):
print("captured line 1")
print("captured line 2")
text = buffer.getvalue()
print("Captured", len(text.splitlines()), "lines")
print(repr(text))
# ✅ Expected output:
# No crash - the error was suppressed
# Captured 2 lines
# 'captured line 1\ncaptured line 2\n'3 ExitStack and closing()
ExitStack manages a dynamic number of context managers. You call stack.enter_context(cm) for each one, and when the with block ends, all of them are exited in reverse order. Below, three resources are opened from a list and closed automatically:
from contextlib import ExitStack, contextmanager
@contextmanager
def resource(name):
print(f"open {name}")
yield name
print(f"close {name}")
names = ["db", "cache", "log"]
with ExitStack() as stack:
opened = [stack.enter_context(resource(n)) for n in names]
print("Using:", opened)
# closes in reverse order: log, cache, dbAnd closing() adapts any object with a close() method into a context manager:
from contextlib import closing
class Connection:
def __init__(self):
print("connected")
def close(self):
print("closed")
def query(self):
return "rows: 3"
with closing(Connection()) as conn:
print(conn.query())
# connected / rows: 3 / closedfrom contextlib import ExitStack, contextmanager
@contextmanager
def resource(name):
print(f"open {name}")
yield name
print(f"close {name}")
names = ["db", "cache", "log"]
with ExitStack() as stack:
opened = [stack.enter_context(resource(n)) for n in names]
print("Using:", opened)
# ✅ Expected output:
# open db
# open cache
# open log
# Using: ['db', 'cache', 'log']
# close log
# close cache
# close db🎯 Your Turn: Fill in the Blanks
Replace each ___ to finish a context manager that prints a banner before and after the block.
from contextlib import contextmanager
@___ # hint: the decorator
def banner(title):
print("=" * 10)
print(title)
___ # hint: hand control to the with-block
print("=" * 10)
with banner("REPORT"):
print("line of data")
# Expected output:
# ==========
# REPORT
# line of data
# ==========⚠️ Common Errors
❌ Forgetting to yield (or yielding twice)
@contextmanager
def bad():
print("setup")
# no yield -> RuntimeError when used in a with@contextmanager
def good():
print("setup")
yield
print("cleanup")with suppress(Exception): # hides real bugs!
risky()✅ Suppress only what you expect
with suppress(FileNotFoundError):
os.remove(path)❌ Putting cleanup outside the generator
If cleanup must always run, it belongs after the yield (ideally guarded by try/finally), not after the with block — otherwise an exception inside the block skips it.
🎯 Mini Challenge: Capture a Labeled Job
Write a timer_label context manager that prints a start and end line, run it inside redirect_stdout to capture everything, then report how many lines were captured.
import io
from contextlib import contextmanager, redirect_stdout
@contextmanager
def timer_label(label):
print(f"[{label}] start")
yield
print(f"[{label}] end")
buf = io.StringIO()
with redirect_stdout(buf):
with timer_label("job"):
print("working...")
captured = buf.getvalue()
print("Lines captured:", len(captured.splitlines()))
print(captured, end="")
# ✅ Expected output:
# Lines captured: 3
# [job] start
# working...
# [job] end📚 Keep Learning
- • contextlib — Utilities for with-statement contexts
- • contextlib.ExitStack
- • Data model: context managers (__enter__/__exit__)
Frequently Asked Questions
Lesson complete — you write your own context managers!
You can build setup/cleanup logic with @contextmanager and a single yield, ignore precise errors with suppress(), capture output with redirect_stdout, manage runtime-sized resource sets with ExitStack, and adapt close()-able objects with closing().
Practice quiz
What does the @contextmanager decorator turn a generator into?
- An async function
- A regular list
- A reusable context manager you can use with 'with'
- A decorator factory
Answer: A reusable context manager you can use with 'with'. @contextmanager turns a simple generator into a full context manager usable with 'with'.
In a @contextmanager generator, what does the code BEFORE the yield represent?
- The setup that runs on entering the with block
- The cleanup
- The exception handler
- Dead code
Answer: The setup that runs on entering the with block. Code before yield is the setup, running when the with block is entered.
What becomes the 'as' variable in a @contextmanager generator?
- The function name
- Always None
- The generator object
- The value passed to yield
Answer: The value passed to yield. The value yielded becomes the variable bound by 'as' in the with statement.
How many times must a @contextmanager generator yield?
- Zero times
- Exactly once
- Twice
- At least three times
Answer: Exactly once. It must yield exactly once; yielding zero or multiple times raises a RuntimeError.
What does contextlib.suppress(FileNotFoundError) do?
- Cleanly ignores that specific exception, like try/except: pass
- Raises FileNotFoundError
- Logs the error to a file
- Retries the operation
Answer: Cleanly ignores that specific exception, like try/except: pass. suppress(SomeError) swallows that specific exception so the program continues — a clean try/except: pass.
Why is 'with suppress(Exception):' considered bad practice?
- It is slower
- It only works in Python 3.11+
- It hides real bugs by swallowing everything broadly
- It raises a RuntimeError
Answer: It hides real bugs by swallowing everything broadly. Suppressing Exception broadly hides genuine bugs; suppress only the specific error you expect.
What does redirect_stdout(buffer) do?
- Deletes printed output
- Captures everything print() writes into the buffer instead of the screen
- Speeds up printing
- Redirects input from a file
Answer: Captures everything print() writes into the buffer instead of the screen. redirect_stdout(buffer) captures print() output into a string buffer like io.StringIO.
What problem does contextlib.ExitStack solve?
- Making code run in parallel
- Suppressing all output
- Replacing for-loops
- Managing a dynamic, unknown-at-write-time number of context managers
Answer: Managing a dynamic, unknown-at-write-time number of context managers. ExitStack lets you enter a runtime-determined number of context managers and clean them all up.
In what order does ExitStack exit the contexts it entered?
- The same order they were entered
- Reverse order
- Random order
- Alphabetical order
Answer: Reverse order. ExitStack exits the entered contexts in reverse order when the block ends.
What does contextlib.closing(obj) do?
- Opens a file
- Suppresses errors
- Adapts any object with a close() method into a context manager
- Creates a generator
Answer: Adapts any object with a close() method into a context manager. closing(obj) wraps an object so its close() method is called automatically at block end.
Continue this course
- Previous: Thread-Safe Queues (queue)
- Next: Temporary Files & Directories — Create auto-cleaning temp files and folders with the tempfile module
- Quick reference: Python cheat sheet
Frequently asked questions
What does @contextmanager actually do?
It turns a simple generator function into a full context manager you can use with 'with'. Everything before the 'yield' runs on entry (setup); the value you yield becomes the 'as' variable; everything after the yield runs on exit (cleanup). It saves you from writing a class with __enter__ and __exit__ methods.
When should I use contextlib.suppress?
Use suppress(SomeError) when you genuinely want to ignore a specific exception and continue. It replaces a try/except pass block with one clean line: 'with suppress(FileNotFoundError): os.remove(path)'. Be specific about which exceptions you suppress — never suppress Exception broadly, or you'll hide real bugs.
What problem does ExitStack solve?
ExitStack lets you enter a variable, unknown number of context managers at runtime and guarantees they all get cleaned up — in reverse order — when the block ends. It's perfect when you need to open a list of files whose count you don't know until the program runs, replacing deeply nested with-statements.
How is closing() different from a plain with?
Some objects have a close() method but don't support the 'with' protocol themselves. contextlib.closing(obj) wraps any such object so that obj.close() is called automatically when the block ends. It's a small adapter that brings non-context-manager resources into the safe 'with' world.