On Friday, July 12, 2019, 07:48:52 AM PDT, Joao S. O. Bueno 
<jsbu...@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:
> Python recipe to enter an arbitrary number of contexts at once


| 
| 
| 
|  |  |

 |

 |
| 
|  | 
Python recipe to enter an arbitrary number of contexts at once

Python recipe to enter an arbitrary number of contexts at once - itercontext.py
 |

 |

 |



> (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.

  
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/GHECHV7V4S2VQ35D2GBRKWFQ5PYDIXXE/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to