In Python, you can intercept what gets printed to the screen via print().
This is useful for testing, debugging, logging, or suppressing noisy output.
I will show you how to do this elegantly — using only the standard library and a few lines of code.
In This Article
🧩 Understanding Context Managers in Python
What is a Context Manager?
A context manager manages resources safely and cleanly.
It defines setup and cleanup behavior around a block of code.
Example: file handling
with open("file.txt", "w") as f:
f.write("Hello, world!")
✅ Automatic cleanup: the file is closed even if an exception occurs.
Why Context Managers Matter
- Automatic Cleanup – resources are released automatically
- Exception Safety – cleanup runs even if errors occur
- Readable Code – encapsulates setup/teardown
- Reusable – works for files, locks, network connections, or stdout
Using @contextmanager from contextlib
Python’s @contextmanager allows writing context managers using a generator:
from contextlib import contextmanager
@contextmanager
def my_context():
print("Entering context")
resource = "some resource"
try:
yield resource
finally:
print("Exiting context")
with my_context() as res:
print("Inside context:", res)
Output:
Entering context
Inside context: some resource
Exiting context
💡 Explanation:
- Code before
yield→ runs at entry yieldvalue → assigned toas res- Code after
yield→ runs at exit, even on exceptions
This mechanism is perfect for temporarily changing global state, like capturing stdout.
⚡ TL;DR: Context Managers for stdout/stderr
| Task / Scenario | Recommended Context Manager |
|---|---|
Capture only print output | capture_stdout |
| Capture only errors/exceptions | capture_stderr |
| Capture both stdout and stderr | capture_output |
| Capture and display simultaneously | capture_stdout_tee / capture_output_tee |
📦 Capturing stdout
from contextlib import contextmanager
import sys, io
@contextmanager
def capture_stdout():
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stdout = old_stdout
Usage:
with capture_stdout() as out:
print("This output was captured")
print("Captured:", out.getvalue())
🔧 Capturing stderr or Both Streams
Capture stderr
@contextmanager
def capture_stderr():
old_stderr = sys.stderr
sys.stderr = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stderr = old_stderr
Capture stdout + stderr
@contextmanager
def capture_output():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
🔀 Tee Mode: Capture AND Display
class Tee(io.StringIO):
def __init__(self, original):
super().__init__()
self.original = original
def write(self, text):
self.original.write(text)
super().write(text)
def flush(self):
self.original.flush()
Context managers:
@contextmanager
def capture_stdout_tee():
old_stdout = sys.stdout
sys.stdout = tee = Tee(old_stdout)
try:
yield tee
finally:
sys.stdout = old_stdout
@contextmanager
def capture_output_tee():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = tee = Tee(old_stdout)
try:
yield tee
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
Usage:
with capture_stdout_tee() as out:
print("Visible AND captured")
print("Tee captured:", out.getvalue())
📦 Full File: stdout_capture.py
"""
Utility for capturing stdout and stderr with context managers.
Supports:
- stdout capture
- stderr capture
- combined capture
- tee mode
"""
import sys, io
from contextlib import contextmanager
@contextmanager
def capture_stdout():
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
try: yield buffer
finally: sys.stdout = old_stdout
@contextmanager
def capture_stderr():
old_stderr = sys.stderr
sys.stderr = buffer = io.StringIO()
try: yield buffer
finally: sys.stderr = old_stderr
@contextmanager
def capture_output():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = buffer = io.StringIO()
try: yield buffer
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
class Tee(io.StringIO):
def __init__(self, original):
super().__init__()
self.original = original
def write(self, text):
self.original.write(text)
super().write(text)
def flush(self):
self.original.flush()
@contextmanager
def capture_stdout_tee():
old_stdout = sys.stdout
sys.stdout = tee = Tee(old_stdout)
try: yield tee
finally: sys.stdout = old_stdout
@contextmanager
def capture_output_tee():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = tee = Tee(old_stdout)
try: yield tee
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
✅ Usage Examples
from stdout_capture import capture_stdout, capture_output, capture_stdout_tee
def test_func():
print("Hello from function")
# Capture stdout
with capture_stdout() as out:
test_func()
print("Captured stdout:", out.getvalue())
# Capture stdout + stderr
try:
with capture_output() as out:
print("Something")
raise Exception("Oops!")
except Exception:
pass
print("Captured output:", out.getvalue())
# Capture and display
with capture_stdout_tee() as out:
print("Visible AND captured")
print("Tee captured:", out.getvalue())
⚠️ Practical Tips & Pitfalls
1️⃣ Handling Large Output
io.StringIOmay consume too much memory for huge outputs.- Use a temporary file:
import sys, tempfile
with tempfile.TemporaryFile(mode='w+') as tmp:
old_stdout = sys.stdout
sys.stdout = tmp
try:
print("A lot of data...")
tmp.seek(0)
captured = tmp.read()
finally:
sys.stdout = old_stdout
2️⃣ Subprocess Output
capture_stdout()does not capture subprocess output.- Use
subprocess.PIPE:
import subprocess
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)
3️⃣ Thread-Safety
sys.stdoutandsys.stderrare global. Avoid concurrent captures in multiple threads.
4️⃣ When to Prefer Tee
- Debugging functions while logging
- Interactive CLI tools
- Integration tests with terminal feedback
5️⃣ Reset Streams
- Always restore
sys.stdout/sys.stderrinfinally.
6️⃣ Avoid Over-Nesting
- Nesting multiple captures can mix outputs. Use a single context per scenario.
7️⃣ Testing Best Practices
- Unit tests →
capture_stdout()orcapture_output() - Manual debugging → Tee mode
- Assert buffer contents, not side effects