[Python-ideas] PEP 380 close and contextmanagers?

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