in search of graceful co-routines

Chris Withers chris at simplistix.co.uk
Wed May 18 02:00:20 EDT 2011


On 18/05/2011 03:10, Terry Reedy wrote:
> By default, Python iterators operate in pull mode -- consumers request a
> new item when they want one. I believe .send was mostly intended to
> reverse that, to operate in push mode where producers .send() a item to
> a consumer when they are ready to. That is certainly true of examples I
> have seen.

My first exposure was with the @inlineCallbacks decorator in twisted, 
which does use it both ways...

> Using .send for feedback to a provider is trickier, as the two other
> posts have shown.

The closest I've found to something graceful is:

def mygenerator(*args):
     for arg in args:
         print "yielding:",arg
         result = yield arg
         print "returned:",result
         if result is not None:
             yield None

provider = mygenerator(1,2,3)
for arg in provider:
     print "got:",arg
     if arg%2:
         provider.send('hello')

However, if you do g.send(None), you still get a result back, which 
feels a bit weird...

It's pretty disappointing that neither the send nor throw methods added 
as part of PEP342 were provided with a parameter or variant that did 
"send an item but don't advance the generator".

 > Another option is to write an iterator class instead
> of generator function. You can then give the provider a message receive
> method (.send or whatever) that is decoupled from the send-next method.

Yes, that's an option I'd considered, however, with a provider class as 
follows:

class Provider:

     def __init__(self,*args):
         self.args = args
         self.current = 0

     def next(self):
         try:
             val = self.args[self.current]
         except:
             raise StopIteration()
         self.current += 1
         print "yielding:",val
         return val

     def send(self,value):
         print "returned:",value

     def __iter__(self):
         return self

provider = Provider(1,2,3)
for arg in provider:
     print "got:",arg
     if arg%2:
         provider.send('hello')

...but that's a lot more code, and allows one of my anti-use cases to 
happen:

provider = Provider(1,2,3)
provider.send("don't want this to be possible")

The generator implementation deals with the above specific case:

   File "test.py", line 12, in <module>
     provider.send('hello')
TypeError: can't send non-None value to a just-started generator

...which is, in itself, a little weird, given that it doesn't protect 
against:

provider = Provider(1,2,3)
val = provider.next()
provider.send("don't want this to be possible")
provider.send("don't want this to be possible")

cheers,

Chris

-- 
Simplistix - Content Management, Batch Processing & Python Consulting
            - http://www.simplistix.co.uk



More information about the Python-list mailing list