On Friday, July 12, 2019, 07:48:52 AM PDT, Joao S. O. Bueno <jsbueno@python.org.br> wrote:

> Modifying the fundamental tuples for doing that is certainly overkill - 
> but maybe a context-helper function in contextlib that would proper handle all the 
> corner cases of some code as I've pasted now at:




> (the link above actually have working code to implement the OP sugestion as a generator-function)

But that code doesn't clean up properly if any of the enters fails, it just leaks the already-entered contexts. And if any of the exits fails, the remainder don't get cleaned up (and the original exception gets lost, too). And it exits them in entry order instead of reverse entry order.

You could fix all of that by writing it around ExitStack, but in that case it's just a one-liner, and in fact the same one-liner that's the first thing in the ExitStack docs:

    with ExitStack() as stack:
        files = [stack.enter_context(open(fname)) for fname in filenames]

Or, for your exact example:

    with ExitStack() as stack:
        files = [stack.enter_context(open(f"file_{i}.bin", "wb")) for i in range(5)]
        for i, file_ in enumerate(files):
            file_.write(bytes(i.to_bytes(1, "little")))

Or, you don't even need to make a list here:

    with ExitStack() as stack:
        files = (stack.enter_context(open(f"file_{i}.bin", "wb")) for i in range(5))
        for i, file_ in enumerate(files):
            file_.write(bytes(i.to_bytes(1, "little")))

And that has the advantage that it can be easily rewritten to be a more unwieldy but easier for novices to follow, the same way as any other comprehension:

    with ExitStack() as stack:
        for i in range(5):
            file = stack.enter_context(open(f"file_{i}.bin", "wb")
            file_.write(bytes(i.to_bytes(1, "little")))

Anyway, as the docs for ExitStack say:

This is a relatively low level API that takes care of the details of correctly unwinding the stack of exit callbacks. It provides a suitable foundation for higher level context managers that manipulate the exit stack in application specific ways.

And your desired API can be written by subclassing or delegating to an ExitStack without needing any advanced code, or any thought about failure handling:

    class itercontext(ExitStack):
        def __init__(self, *cms):
            self._contexts = []
            super().__init__()
            self._contexts.extend(self.enter_context(cm) for cm in cms)
        def __iter__(self):
            yield from self._contexts

… or, if you prefer to think about failure handling in the @contextmanager style:

    @contextmanager
    def itercontext(*cms):
        with ExitStack() as stack:
            contexts = [stack.enter_context(cm) for cm in cms]
            # It may take a comment to convince readers that there's nothing for try/finally to do here?
            yield contexts

There are a few convenience helpers that could be added to ExitStack to make these even easier to write, or even to make it usable out of the box for a wider range of scenarios. An enter_contexts(cms) function could make it clear exactly what failure does to the rest of the cms iterable. Or enter_contexts(*cms), which forces an iterator to be consumed before entering anything (as your example does). Or even __init__(*cms). It could expose its managers and/or contexts as an attribute. Ir could even be an iterable or sequence of its contexts. Then, you could just use ExitStack directly as your desired itercontext.

But I don't know that those are worth adding to ExitStack, or even to a higher-level wrapper in the stdlib. I think the real problem isn't that it's too hard for novices to do this themselves as-needed, it's that it's too hard for them to discover ExitStack, to grok what it does, and to realize how easy it is to expand on. Once they get that, they can write itercontext, and anything else they need, themselves. But until they do, they'll try to write what you wrote, and either get it wrong in far worse ways or just give up.

I'm not sure how to fix that. (More links to it in the docs, more examples in its own docs, rewrite the "low-level" sentence so it sounds more like an invitation than a warning,  a HOWTO, vigorous proselytizing‚Ķ?) But I don't think adding one or two wrappers (or, worse, less-powerful partial workalikes) to the same module would help.