🎓 A1Learner
🔍
0 of 102 lessons completed

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?