Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send method using generator. still trying to understand the send method and quirky behaviour

Here is a little function i wrote to understand the send method:

>>> def test():
...     for x in xrange(10):
...         res = yield
...         yield res
>>> a = test()
>>> next(a)
>>> next(a)
>>> next(a)
>>> next(a)
>>> a.send(0)
Traceback (most recent call last):
   <ipython-input-220-4abef3782000> in <module>()
StopIteration
>>> a = test()
>>> a.send(0)
Traceback (most recent call last):
   <ipython-input-222-4abef3782000> in <module>()    
TypeError: can't send non-None value to a just-started generator
>>> a.send(None)
>>> a.send(0)
0
>>> a.send(0)
>>> a.send(0)
0
>>> a.send(0)
>>> a.send(0)
0
>>> a.send(0)

Why is there an Error the first time?

>>> a.send(0)
StopIteration 

Why does it require for the first send() to be None? as with this error:

>>> a.send(0)
Traceback (most recent call last):
    <ipython-input-222-4abef3782000> in <module>()
TypeError: can't send non-None value to a just-started generator

And then the first send starts the generator( i dont know why) and i send a '0' and it prints it but the second 0 is again none and resumes with whatever i send it(0 here)

>>> a.send(None)
>>> a.send(0)
0
>>> a.send(0)
>>> a.send(0)
0
>>> a.send(0)
>>> a.send(0)
0

This link doesnt help much Python 3: send method of generators

like image 410
user2290820 Avatar asked Nov 10 '13 16:11

user2290820


People also ask

What is send type in generator?

The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.

How do you create a generator object in Python?

Create Generators in Python It is fairly simple to create a generator in Python. It is as easy as defining a normal function, but with a yield statement instead of a return statement. If a function contains at least one yield statement (it may contain other yield or return statements), it becomes a generator function.


2 Answers

Why does it require for the first send() to be None?

You can't send() a value the first time because the generator did not execute until the point where you have the yield statement, so there is nothing to do with the value.

Here is the relevant paragraph from the pep that introduced the feature of co-routines with generators (http://www.python.org/dev/peps/pep-0342/):

Because generator-iterators begin execution at the top of the generator's function body, there is no yield expression to receive a value when the generator has just been created. Therefore, calling send() with a non-None argument is prohibited when the generator iterator has just started, and a TypeError is raised if this occurs (presumably due to a logic error of some kind). Thus, before you can communicate with a coroutine you must first call next() or send(None) to advance its execution to the first yield expression

A tiny walkthrough:

def coro():
   print 'before yield'
   a = yield 'the yield value'
   b = yield a
   print 'done!'
 c=coro() # this does not execute the generator, only creates it

 # If you use c.send('a value') here it could _not_ do anything with the value
 # so it raises an TypeError! Remember, the generator was not executed yet,
 # only created, it is like the execution is before the `print 'before yield'`

 # This line could be `c.send(None)` too, the `None` needs to be explicit with
 # the first use of `send()` to show that you know it is the first iteration
 print next(c) # will print 'before yield' then 'the yield value' that was yield

 print c.send('first value sent') # will print 'first value sent'

 # will print 'done!'
 # the string 'the second value sent' is sent but not used and StopIterating will be raised     
 print c.send('the second value sent') 
 
 print c.send('oops') # raises StopIterating
like image 56
Augusto Hack Avatar answered Sep 18 '22 17:09

Augusto Hack


The send() can only be called if generator is waiting at yield. Since your generator hasn't started executing, calling send() gives you the first error.

Further more, each yield in the generator consumes the value sent by send(). When the second yield statement consumes value but it doesn't use it so that value is discarded. Then you wait on first yield which does consumes value from send() and that value is printed out. So you end up with needing two send().

Here's the fixed version:

>>> def echo():            
...   while True:           
...     val = (yield) 
...     yield val           
...                         
>>> g=echo()               
>>> next(g)    # move to 1st yield
>>> g.send(2)  # execution stops at 2nd yield
2                           
>>> next(g)    # execution stops at 1st yield
>>> g.send(3)  # execution stops at 2nd yield      
3                           
like image 28
Shital Shah Avatar answered Sep 16 '22 17:09

Shital Shah