使用生成器的发送方法。仍在尝试理解“send”方法和奇怪的行为。

18

这是我编写的一个小函数,用于理解发送方法:

>>> 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)

第一次出现错误的原因是什么?
>>> a.send(0)
StopIteration 

为什么第一次 send() 需要是 None?这与以下错误有关:
>>> 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

然后第一个发送开始发电机(我不知道为什么),我发送一个“0”,它会打印出来,但第二个“0”再次是无,然后继续使用我发送的内容(这里是0)。

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

这个链接并没有太大帮助 Python 3: 生成器的send方法

3个回答

20

为什么第一次send()需要传入None?

在生成器执行到yield语句之前,你不能通过send()方法发送值,因为此时没有东西可以处理这个值。

下面是介绍使用生成器实现协程的pep文档中相关的段落(http://www.python.org/dev/peps/pep-0342/):

由于生成器迭代器从生成器函数体的顶部开始执行,当生成器刚创建时并没有yield表达式来接收一个值。 因此,当生成器迭代器刚开始时,禁止使用非None参数调用send()方法;如果发生这种情况(可能是某种逻辑错误),则会引发TypeError异常。 因此,在与协程通信之前,必须先调用next()或send(None)将它们的执行推进到第一个yield表达式。

简要演示:

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

为什么这个被丢弃了? "c.send('发送的第二个值')" - user2290820
第二个 send() 中的值将被分配给 coro() 内部的变量 b,但是这个值会被丢弃,因为从 b = yield a 这一行到协程结束时,变量 b 没有被用于任何操作。 - Augusto Hack
那么 c.send('first value sent') 是如何使用的呢?如果你说它被 yield a 使用了,那么这是否意味着如果还有另一行 yield c,那么它就不会被丢弃了呢? - user2290820
抱歉,discarded 不是一个好的选择。该值已发送,b 将包含值 the second value sent,如果您有第三个 yield,它将接收下一个值 send(),以此类推。您可以自行处理发送的值,我只是想说您不必使用该对象。 - Augusto Hack
第一次确实存在错误,所以你的答案是错误的。你没有理解它,因为你立即在该对象上调用了“next”。如果你先创建它并尝试发送数据,你将会得到问题中提到的错误。 - fuuman

4
send() 只能在生成器等待 yield 时调用。由于您的生成器尚未开始执行,调用 send() 将会导致第一个错误。
此外,生成器中的每个 yield 都会消耗 send() 发送的值。当第二个 yield 语句消耗值但不使用它时,该值被丢弃。然后您等待第一个 yield,该 yield 会从 send() 消耗值并打印出该值。因此,您最终需要两个 send()
以下是修复后的版本:
>>> 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                           

0

PEP 342 新生成器方法:send(value)说:

调用send(None)与调用生成器的next()方法完全相同。使用任何其他值调用send()是相同的,除了由生成器当前yield表达式产生的值将不同。

因为生成器迭代器从生成器函数体的顶部开始执行,所以在创建生成器时没有yield表达式可以接收值。因此,在生成器迭代器刚开始时禁止使用非None参数调用send(),如果发生这种情况,则会引发TypeError(可能是某种逻辑错误)。因此,在与协程通信之前,必须先调用next()或send(None)将其执行推进到第一个yield表达式


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接