ExitStack stores cleanup operations, and pop_all moves those operations into another stack without running them.
Python ExitStack pop_all: transfer cleanup without discarding it
Operation contract
The acquisition stack registers release callbacks for a receipt stream and its index. A completed setup transfers both callbacks to the returned owner. Exiting the acquisition block now has nothing to release. The caller closes the owner later, which runs index release before stream release. A failed setup takes the other path: exiting the acquisition block releases its partial resources immediately. The log is an owned list rather than a real storage service, so the order can be checked without filesystem effects.
Failure and ownership boundary
Transfer is not cancellation of cleanup. A caller that loses the returned stack also loses its explicit release boundary; object collection is not a substitute for close. Callbacks registered with callback cannot suppress exceptions through a return value. Failures in cleanup need their own policy. Python ExitStack: unwind partially acquired resources and Python context managers: clean up on success and failure establish the preceding contracts.
Working program
from contextlib import ExitStack
def acquire_bundle(events, fail=False):
with ExitStack() as pending:
pending.callback(events.append, "stream released")
pending.callback(events.append, "index released")
if fail:
raise ValueError("index validation")
return pending.pop_all()
events = []
owner = acquire_bundle(events)
print("before close:", events)
owner.close()
owner.close()
print("after close:", events)
failed_events = []
try:
acquire_bundle(failed_events, fail=True)
except ValueError:
print("failed setup:", failed_events)Output
before close: []
after close: ['index released', 'stream released']
failed setup: ['index released', 'stream released']Costs and limits
Registering k callbacks retains O(k) references. Closing executes them in reverse registration order, plus each callback cost. pop_all transfers the registered stack rather than traversing resources to release them. This fixture does not test operating-system handle exhaustion or a callback that fails.
Common Mistakes
- Retain and close the new owner after pop_all.
- A successful setup must not silently abandon its cleanup callbacks.
Connected lessons
Python ExitStack: unwind partially acquired resources, Python context managers: clean up on success and failure, Python generator send and close: suspension retains state until cleanup.
