exitstack-patterns.md

reference

← Back to skill

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