Add `__init__(self, contexts: Iterable[Contextmanager])` to `contextlib.ExitStack`

A quite common use-case for contextlib.ExitStack is to support variable number of contextmanager:

with contextlib.ExitStack() as stack:
    for resource in resources:
        stack.enter_context(resource)
    ...

However this feel quite verbose. When the output of the __enter__ method is not needed, it would be nice if the above code could be simplified as:

with contextlib.ExitStack(resources):
    ...

It is a very minor change but I feel would reduce boilerplate in quite a few common places, like:

2 Likes

See contextlib.nested().
https://docs.python.org/3.1/library/contextlib.html#contextlib.nested

contextlib.nested has documented limitations and is deprecated, so the initial request makes sense.

What would the implementation look like? It would be strange to call enter_context from __init__, so save the managers internally and enter them in __enter__?

The requested feature has exactly the same design flaws as contextlib.nested. It would be unsafe to revive contextlib.nested under other name.

3 Likes

Unless I am misunderstanding something, the proposed feature actually differs from nested in one specific way: it accepts an iterator, which may be lazy.

Compare:

with nested(*(open(f) for f in files)):
    pass

and

with ExitStack(open(f) for f in files):
    pass

The nested version needs to materialize all the items to pass them as arguments, while the ExitStack version can save the lazy iterator and then lazily consume it in its __enter__ method.

Of course, there is still a problem that you could accidentally do something like

with ExitStack([open(f) for f in files]):
    pass

resulting in the same incorrect behavior as nested.

Unless we have a way to detect whether the passed iterator is lazy, I am inclined to agree, that this proposal has the same problem as nested.

1 Like