Python ExitStack & Friends
ExitStack is a context manager that collects other managers and exits them all.
Enter managers in a loop
This snippet assumes two files. The file a.txt holds Apples and b.txt holds Bread.
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(name)) for name in ["a.txt", "b.txt"]]
print([f.read() for f in files])Callbacks run in reverse order
A callback is plain code saved for later. The stack runs callbacks last registered first.
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(print, "first registered")
stack.callback(print, "second registered")
print("work")nullcontext() when no manager is needed
Sometimes a manager is optional. The nullcontext helper hands back a value and does nothing on exit.
from contextlib import nullcontext
needs_file = False
manager = open("a.txt") if needs_file else nullcontext("just text")
with manager as value:
print(value)redirect_stdout captures prints
Prints inside the block go into a buffer instead. A buffer is a piece of memory that holds text.
import io
from contextlib import redirect_stdout
buffer = io.StringIO()
with redirect_stdout(buffer):
print("Hidden hello")
captured = buffer.getvalue()
print(captured.strip())
print("Shown again")Example
import io
from contextlib import ExitStack, redirect_stdout
data = {
"part1.txt": "apples\nmilk\n",
"part2.txt": "bread\ncheese\n",
"part3.txt": "tea\nhoney\n",
}
for name, text in data.items():
with open(name, "w") as file:
file.write(text)
names = ["part1.txt", "part2.txt", "part3.txt"]
merged = []
with ExitStack() as stack:
files = [stack.enter_context(open(name)) for name in names]
for file in files:
for line in file:
merged.append(line.strip())
print("Merged lines:")
for line in merged:
print("-", line)
buffer = io.StringIO()
with redirect_stdout(buffer):
print("Report complete")
captured = buffer.getvalue()
print("Captured:", repr(captured))Key points
- ExitStack: a context manager that collects other managers and exits them all
- Use stack.enter_context(manager) to enter one manager and keep its as value
- Use stack.callback(fn) to register plain cleanup code, with no manager needed
- Managers and callbacks exit in reverse order, last entered first
- Use nullcontext(value) when a step may need no real manager at all
- Use redirect_stdout(target) to send print output into a text buffer
In this example, the first loop writes three small files. Their names are part1.txt, part2.txt, and part3.txt. The ExitStack block opens all three files in one comprehension. Each enter_context call keeps one file open. The nested loops read every line in order. Each line is stripped and added to the merged list. All three files close when the stack block ends. The prints then list the merged lines. A redirect_stdout block sends one print into a buffer. The last line prints the captured text with repr. The repr shows the trailing newline as an escape.
Note
ExitStack exits everything in reverse order.
That mirrors nested with blocks, where the innermost manager exits first.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
You entered three managers into an ExitStack, in order A, B, C. In what order do they exit?