[Python-ideas] PEP 380 close and contextmanagers? (original) (raw)

Ron Adam rrr at ronadam.com
Thu Oct 28 02:00:52 CEST 2010


On 10/27/2010 01:38 PM, Guido van Rossum wrote:

On Wed, Oct 27, 2010 at 9:18 AM, Ron Adam<rrr at ronadam.com> wrote:

On 10/27/2010 10:01 AM, Ron Adam wrote: It looks like No context managers return values in the finally or exit part of a context manager. Is there way to do that? How would that value be communicated to the code containing the with-clause?

I think that was what I was trying to figure out also.

def reducei(f): i = yield while True: i = f(i, (yield i)) Unfortunately from here on till the end of your example my brain exploded.

Mine did too, but I think it was a useful but strange experience. ;-)

It forced me to take a break and think about the problem from a different viewpoint. Heres the conclusion I came to, but be forewarned, it's kind of anti-climatic. :-)

The use of an exception to signal some bit of code, is a way to reach over a wall that also protects that bit of code. This seems to be a more common need when using coroutines, because it's more common to have some bits of code indirectly, direct some other bit of code.

Generators already have a nice .throw() method that will return the value at the next yield. But we either have to choose an existing exception to throw, that has some other purpose, or make up a new one. When it comes to making up new ones, lots of other programmers may each call it something else.

That isn't a big problem, but it may be nice if we had a standard exception for saying.. "Hey you!, send me a total or subtotal!". And that's all that it does. For now lets call it a ValueRequest exception.

ValueRequest makes sense if you are throwing an exception, I think ValueReturn may make more sense if you are raising an exception. Or maybe there is something that reads well both ways? These both fit very nice with ValueError and it may make reading code easier if we make a distinction between a request and a return.

Below is the previous example rewritten to do this. A ValueRequest doesn't stop anything or force anything to close, so it wont ever interfere, confuse, or complicate, code that uses other exceptions. You can always throw or catch one of these and raise something else if you need to.

Since throwing it into a generator doesn't stop the generator, the generator can put the try-except into a larger loop and loop back to get more values and catch another ValueRequest at some later point. I feel that is a useful and handy thing to do.

So here's the example again.

The first version of this took advantage of yield's ability to send and get data at the same time to always send back an update (subtotal) to the parent routine. That's nearly free since a yield always sends something back anyway. (None if you don't give it something else.) But it's not always easy to do, or easy to understand if you do it. IE.. brain exploding stuff.

In this version, data only flows into the coroutine until a ValueRequest exception is thrown at it, at which point it then yields back a total.

*I can see where some routines may reverse the control, by throwing ValueReturns from the inside out, rather than ValueRequests from the outside in. Is it useful to distinquish between the two or should there be just one?

*Yes this can be made to work with gclose() and return, but I feel that is more restrictive, and more complex, than it needs to be.

*I still didn't figure out how to use the context managers to get rid of the try except. Oh well. ;-)

from contextlib import contextmanager

class ValueRequest(Exception): pass

@contextmanager def consumer(cofunc, result=True): next(cofunc) try: yield cofunc finally: cofunc.close()

@contextmanager def multiconsumer(cofuncs, result=True): for c in cofuncs: next(c) try: yield cofuncs finally: for c in cofuncs: c.close()

Min/max coroutine example slpit into

nested coroutines for testing these ideas

in a more complex situation that may arise

when working with cofunctions and generators.

def reduce_item(f): try: x = yield while True: x = f(x, (yield)) except ValueRequest: yield x

def reduce_group(funcs): with multiconsumer([reduce_item(f) for f in funcs]) as mc: try: while True: x = yield for c in mc: c.send(x) except ValueRequest: yield [c.throw(ValueRequest) for c in mc]

def get_reductions(funcs, iterable): with consumer(reduce_group(funcs)) as c: for x in iterable: c.send(x) return c.throw(ValueRequest)

def main(): funcs = [min, max] print(get_reductions(funcs, range(100))) s = "Python is fun for play, and great for work too." print(get_reductions(funcs, s))

if name == 'main': main()



More information about the Python-ideas mailing list