Content hash: 9f8d55d6f5119a98cc403feb8da5aae7990f67cc923b1a78bfd02f9ff8cb0799
## ExitStack Patterns
### Dynamic file opening
```python
from contextlib import ExitStack
filenames = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in filenames]
# All files guaranteed closed on exit
```
### Conditional resource registration
```python
with ExitStack() as stack:
conn = stack.enter_context(sqlite3.connect(":memory:"))
if needs_lock:
stack.enter_context(threading.Lock())
# ...
```
### Callback-style cleanup
```python
with ExitStack() as stack:
resource = acquire()
stack.callback(resource.release)
stack.callback(lambda: print("cleanup done"))
```
### Nested context managers without nesting
```python
# Instead of:
with A() as a:
with B() as b:
with C() as c:
...
# Use:
with ExitStack() as stack:
a = stack.enter_context(A())
b = stack.enter_context(B())
c = stack.enter_context(C())
```
### Pitfalls to avoid
- `__exit__` returning `True` swallows exceptions -- almost never correct
- Generator-based managers (`@contextmanager`) can't be reused after close
- Don't yield a resource that was already closed before the `yield`
- Forgetting `super().__exit__()` in subclassed managers