[Python-ideas] PEP 380 close and contextmanagers?

Ron Adam rrr at ronadam.com
Wed Oct 27 18:18:58 CEST 2010



On 10/27/2010 10:01 AM, Ron Adam wrote:
>
> On 10/25/2010 10:25 PM, Guido van Rossum wrote:
>> By the way, here's how to emulate the value-returning-close() on a
>> generator, assuming the generator uses raise StopIteration(x) to mean
>> return x:
>>
>> def gclose(gen):
>> try:
>> gen.throw(GeneratorExit)
>> except StopIteration, err:
>> if err.args:
>> return err.args[0]
>> except GeneratorExit:
>> pass
>> return None
>>
>> I like this because it's fairly straightforward (except for the detail
>> of having to also catch GeneratorExit).
>>
>> In fact it would be a really simple change to gen_close() in
>> genobject.c -- the only change needed there would be to return
>> err.args[0]. I like small evolutionary improvements to APIs.
>
> Here's an interesting idea...
>
> It looks like a common case for consumer co-functions is they need to be
> started and then closed, so I'm wondering if we can make these work
> context managers? That may be a way to reduce the need for the
> try/except blocks inside the generators.

It looks like No context managers return values in the finally or __exit__ 
part of a context manager.  Is there way to do that?

Here's a context manager version of the min/max with nested coroutines, but 
it doesn't return a value from close.


######
from contextlib import contextmanager


# New close function that enables returning a
# value.

def gclose(gen):
    try:
      gen.throw(GeneratorExit)
    except StopIteration as err:
      if err.args:
        return err.args[0]
    except GeneratorExit:
      pass
    return None


# Showing  both the class and geneator based
# context managers for comparison and to better
# see how these things may work.

class Consumer:
     def __init__(self, cofunc):
         next(cofunc)
         self.cofunc = cofunc
     def __enter__(self):
         return self.cofunc
     def __exit__(self, *exc_info):
         gclose(self.cofunc)

@contextmanager
def consumer(cofunc):
     next(cofunc)
     try:
         yield cofunc
     finally:
         gclose(cofunc)


class MultiConsumer:
     def __init__(self, cofuncs):
         for c in cofuncs:
             next(c)
         self.cofuncs = cofuncs
     def __enter__(self):
         return self.cofuncs
     def __exit__(self, *exc_info):
         for c in self.cofuncs:
             gclose(c)

@contextmanager
def multiconsumer(cofuncs):
     for c in cofuncs:
         next(c)
     try:
         yield cofuncs
     finally:
         for c in cofuncs:
             gclose(c)


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

# Question:
#    How to rewrite this so close returns
#    a final value?

def reduce_i(f):
      i = yield
      while True:
          i = f(i, (yield i))

def reduce_it_to(funcs):
     with multiconsumer([reduce_i(f) for f in funcs]) as mc:
         values = None
         while True:
             i = yield values
             values = [c.send(i) for c in mc]

def main():
     with consumer(reduce_it_to([min, max])) as c:
         for i in range(100):
             value = c.send(i)
         print(value)


if __name__ == '__main__':
     main()





More information about the Python-ideas mailing list