我理解让步。但是生成器的send函数是做什么的呢?文件说:

generator.send(值) 恢复执行并向生成器函数“发送”一个值。value参数变成当前yield表达式的结果。send()方法返回生成器产生的下一个值,如果生成器退出而没有产生另一个值,则引发StopIteration。

这是什么意思?我以为value是生成器函数的输入?短语“send()方法返回生成器产生的下一个值”似乎也是yield的确切目的,它也返回生成器产生的下一个值。

有没有一个生成器利用send完成了yield不能完成的事情的例子?


当前回答

send()方法控制yield表达式左边的值。

为了理解yield的不同之处以及它所代表的值,让我们首先快速刷新python代码被求值的顺序。

第6.15节评估命令

Python从左到右计算表达式。注意,在计算赋值时,右边的值比左边的值先计算。

所以表达式a = b首先求右边的值。

如下所示,a[p('left')] = p('right')首先计算右边的值。

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

收益率是做什么的?, yield将暂停函数的执行并返回给调用者,并在暂停之前停止的同一位置恢复执行。

死刑到底在哪里暂缓执行?你可能已经猜到了…… 在yield表达式的左右之间暂停执行。因此new_val = yield old_val执行在=号处停止,并且右边的值(挂起之前,也是返回给调用者的值)可能与左边的值(恢复执行后分配的值)不同。

Yield产生两个值,一个在右边,另一个在左边。

如何控制yield表达式左边的值?通过.send()方法。

6.2.9. 产量表达式

恢复后的yield表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过for或next()内置函数),则结果为None。否则,如果使用send(),则结果将是传递给该方法的值。

其他回答

这可能会对某些人有所帮助。这是一个不受发送函数影响的生成器。它在实例化时接受number参数,不受send的影响:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

下面是你如何使用send来执行相同类型的函数,所以在每次迭代中你都可以改变number的值:

def double_number(number):
    while True:
        number *= 2
        number = yield number

下面是它的样子,你可以看到为number发送一个新值会改变结果:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

你也可以在for循环中这样写:

for x in range(10):
    n = c.send(n)
    print n

要获得更多帮助,请查看这个很棒的教程。

send()方法控制yield表达式左边的值。

为了理解yield的不同之处以及它所代表的值,让我们首先快速刷新python代码被求值的顺序。

第6.15节评估命令

Python从左到右计算表达式。注意,在计算赋值时,右边的值比左边的值先计算。

所以表达式a = b首先求右边的值。

如下所示,a[p('left')] = p('right')首先计算右边的值。

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

收益率是做什么的?, yield将暂停函数的执行并返回给调用者,并在暂停之前停止的同一位置恢复执行。

死刑到底在哪里暂缓执行?你可能已经猜到了…… 在yield表达式的左右之间暂停执行。因此new_val = yield old_val执行在=号处停止,并且右边的值(挂起之前,也是返回给调用者的值)可能与左边的值(恢复执行后分配的值)不同。

Yield产生两个值,一个在右边,另一个在左边。

如何控制yield表达式左边的值?通过.send()方法。

6.2.9. 产量表达式

恢复后的yield表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过for或next()内置函数),则结果为None。否则,如果使用send(),则结果将是传递给该方法的值。

send方法实现协程。

如果你没有遇到过协程,你很难理解它们,因为它们改变了程序的运行方式。你可以阅读一篇好的教程了解更多细节。

它被用来将值发送到一个刚刚产生的生成器中。下面是一个人为的(无用的)解释性例子:

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

你不能只靠产量来做到这一点。

至于为什么它很有用,我见过的最好的用例之一是Twisted的@defer.inlineCallbacks。本质上,它允许你写出这样的函数:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

What happens is that takesTwoSeconds() returns a Deferred, which is a value promising a value will be computed later. Twisted can run the computation in another thread. When the computation is done, it passes it into the deferred, and the value then gets sent back to the doStuff() function. Thus the doStuff() can end up looking more or less like a normal procedural function, except it can be doing all sorts of computations & callbacks etc. The alternative before this functionality would be to do something like:

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

它更加复杂和笨拙。

使用generator和send()的一些用例

使用send()的生成器允许:

记住执行的内部状态 我们走到哪一步了 我们数据的当前状态是什么 返回值序列 接收输入序列

下面是一些用例:

观察尝试遵循食谱

让我们有一个配方,它期望以某种顺序预先定义一组输入。

我们可以:

create a watched_attempt instance from the recipe let it get some inputs with each input return information about what is currently in the pot with each input check, that the input is the expected one (and fail if it is not) def recipe(): pot = [] action = yield pot assert action == ("add", "water") pot.append(action[1]) action = yield pot assert action == ("add", "salt") pot.append(action[1]) action = yield pot assert action == ("boil", "water") action = yield pot assert action == ("add", "pasta") pot.append(action[1]) action = yield pot assert action == ("decant", "water") pot.remove("water") action = yield pot assert action == ("serve") pot = [] yield pot

要使用它,首先创建watched_attempt实例:

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

对.next()的调用是启动生成器执行所必需的。

返回值显示,我们的罐目前是空的。

现在按照食谱的要求做一些动作:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

如我们所见,罐子终于空了。

万一,一个人不遵循配方,它会失败(什么可能是期望的结果观看 试着做点东西——刚知道我们在接到指示时没有足够注意。

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

注意,:

预期的步骤有一个线性序列 步骤可能会有所不同(有些是移除,有些是添加到锅中) 我们通过函数/生成器来实现这一切,不需要使用复杂的类或类似的东西 结构。

运行总数

我们可以使用生成器来跟踪发送给它的值的运行总数。

任何时候我们添加一个数字,输入的计数和总数返回(有效 之前的输入被发送到它的那一刻)。

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

输出如下所示:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)