有人能举例说明为什么与Python生成器函数相关的"发送"功能存在吗?我完全理解屈服函数.但是,发送功能让我很困惑.有关此方法的文档很复杂:
generator.send(value)
Run Code Online (Sandbox Code Playgroud)
恢复执行并将值"发送"到生成器函数中.value参数成为当前yield表达式的结果.send()方法返回生成器产生的下一个值,如果生成器退出而不产生另一个值,则引发StopIteration.
那是什么意思?我认为价值是功能的输入?短语"send()方法返回生成器产生的下一个值"似乎也是yield函数的确切目的; yield返回生成器产生的下一个值...
有人能给我一个利用发送器生成器的例子吗?
Cla*_*diu 118
它用于将值发送到刚刚产生的生成器中.这是一个人为的(无用的)解释性示例:
>>> 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
Run Code Online (Sandbox Code Playgroud)
你不能这样做yield.
至于为什么它有用,我见过的最好的用例之一是Twisted的@defer.inlineCallbacks.基本上它允许你编写这样的函数:
@defer.inlineCallbacks
def doStuff():
result = yield takesTwoSeconds()
nextResult = yield takesTenSeconds(result * 10)
defer.returnValue(nextResult / 10)
Run Code Online (Sandbox Code Playgroud)
会发生什么是takesTwoSeconds()返回a Deferred,这是一个有希望稍后计算出值的值.Twisted可以在另一个线程中运行计算.计算完成后,将其传递给延迟,然后将值发送回doStuff()函数.因此,doStuff()最终可能看起来或多或少像正常的程序函数,除了它可以进行各种计算和回调等.此功能之前的替代方案是执行以下操作:
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
Run Code Online (Sandbox Code Playgroud)
它更复杂,更笨拙.
Dan*_*zer 84
这个函数是编写协同程序
def coroutine():
for i in range(1, 10):
print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
while True:
print("From user {}".format(c.send(1)))
except StopIteration: pass
Run Code Online (Sandbox Code Playgroud)
版画
From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...
Run Code Online (Sandbox Code Playgroud)
看看控制是如何来回传递的?这些是协程.它们可以用于各种很酷的东西,比如asynch IO和类似的东西.
想象一下,有了发电机,没有发送,这是一条单行道
========== yield ========
Generator | ------------> | User |
========== ========
Run Code Online (Sandbox Code Playgroud)
但随着发送,它成为一条双向的街道
========== yield ========
Generator | ------------> | User |
========== <------------ ========
send
Run Code Online (Sandbox Code Playgroud)
这开启了大门,用户自定义生成行为上飞和发电机响应用户.
rad*_*tek 35
这可能对某人有帮助.这是一个不受发送功能影响的生成器.它接受实例化时的数字参数,不受发送的影响:
>>> 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
Run Code Online (Sandbox Code Playgroud)
现在,您将使用send执行相同类型的函数,因此在每次迭代时您都可以更改数字的值:
def double_number(number):
while True:
number *= 2
number = yield number
Run Code Online (Sandbox Code Playgroud)
这就是看起来的样子,因为你可以看到为数字发送一个新值会改变结果:
>>> 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
Run Code Online (Sandbox Code Playgroud)
你也可以把它放在for循环中:
for x in range(10):
n = c.send(n)
print n
Run Code Online (Sandbox Code Playgroud)
有关更多帮助,请查看此精彩教程.
Jan*_*sky 16
send()send()允许的生成器:
以下是一些用例:
让我们有一个配方,它需要某种顺序的预定义输入集.
我们可能:
watched_attempt从食谱中创建一个实例每次输入检查时,输入是预期的输入(如果不是,则输入失败)
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
Run Code Online (Sandbox Code Playgroud)要使用它,首先要创建watched_attempt实例:
>>> watched_attempt = recipe()
>>> watched_attempt.next()
[]
Run Code Online (Sandbox Code Playgroud)
调用.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"))
[]
Run Code Online (Sandbox Code Playgroud)
正如我们所见,锅最终是空的.
如果一个人不遵循食谱,它就会失败(看到尝试烹饪的东西可能是期望的结果 - 只是学习我们在给出指示时没有给予足够的重视.
>>> 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:
Run Code Online (Sandbox Code Playgroud)
请注意:
我们可以使用生成器来跟踪发送给它的运行总值.
每当我们添加一个数字时,都会返回一个输入计数和总和(对于先前输入发送到它的时间有效).
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)
Run Code Online (Sandbox Code Playgroud)
输出看起来像:
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)
Run Code Online (Sandbox Code Playgroud)
use*_*857 13
该send()方法控制yield表达式左侧的值。
为了了解yield的不同以及它保持什么值,我们首先快速刷新评估python代码的顺序。
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]
Run Code Online (Sandbox Code Playgroud)
yield的作用是什么?yield,暂停函数的执行并返回到调用方,并在暂停之前停止的地方恢复执行。
究竟在哪里暂停执行?您可能已经猜到了…… 执行被挂在yield表达式的右侧和左侧之间。因此new_val = yield old_val,执行将在=符号处停止,右侧的值(在挂起之前,并且也是返回给调用方的值)可能与左侧的值(在恢复后分配的值)有所不同执行)。
yield 产生2个值,一个在右边,另一个在左边。
如何控制收益率表达式左侧的值?通过该.send()方法。
恢复后的yield表达式的值取决于恢复执行的方法。如果
__next__()使用if (通常通过for或next()内置),则结果为None。否则,如果send()使用,则结果将是传递给该方法的值。
“产量”这个词有两个含义:生产某物(例如,生产玉米),和停下来让其他人/事物继续(例如,汽车让行给行人)。这两个定义都适用于 Python 的yield关键字;生成器函数的特殊之处在于,与常规函数不同,值可以“返回”给调用者,而只是暂停而不是终止生成器函数。
最容易将生成器想象为具有“左”端和“右”端的双向管道的一端;这个管道是在生成器本身和生成器函数体之间发送值的媒介。管道的每一端都有两个操作:push,发送一个值并阻塞直到管道的另一端拉取该值,并且不返回任何内容;和pull,它会阻塞直到管道的另一端推送一个值,并返回推送的值。在运行时,执行在管道任一侧的上下文之间来回弹跳——每一侧都运行,直到它向另一侧发送一个值,此时它停止,让另一侧运行,并等待输入中的值返回,此时另一侧停止并继续。换句话说,管道的每一端从它接收到一个值到它发送一个值。
管道在功能上是对称的,但是 - 按照惯例,我在这个答案中定义 - 左端仅在生成器函数体内可用,可通过yield关键字访问,而右端是生成器,可通过生成器的send功能。作为它们各自管道末端的单一接口,yield并send执行双重任务:它们都向/从管道末端推送和拉动值,yield向右推向左拉,而send相反。这种双重职责是围绕诸如x = yield y. 打破yield并send分为两个明确的推/拉措施将使它们的语义更加清晰:
g是发电机。g.send通过管道的右端向左推动一个值。g暂停上下文中执行,允许生成器函数的主体运行。g.send被向左拉yield并在管道的左端接收。在x = yield y,x分配给拉取值。yield到达包含的下一行。yield通过管道的左端向右推动一个值,回到g.send。在x = yield y,y被向右推通过管道。g.send 恢复并拉取值并将其返回给用户。g.send一次调用,回到第1步。虽然是循环的,但这个过程确实有一个开始:当g.send(None)——这是next(g)它的缩写——第一次被调用(传递除None第一次send调用之外的其他东西是非法的)。它可能有一个结束:当yield生成器函数的主体中没有更多的语句要到达时。
你看到是什么让yield语句(或更准确地说,生成器)如此特别吗?与 measlyreturn关键字不同,yield它能够将值传递给其调用者并从其调用者接收值,而无需终止其所在的函数!(当然,如果您确实希望终止一个函数——或一个生成器——使用return关键字也很方便。)当yield遇到一个语句时,生成器函数只是暂停,然后从它离开的地方重新开始在发送另一个值时关闭。并且send只是从外部与生成器函数内部进行通信的接口。
如果我们真的要下来,只要我们可以打破这种推/拉/管类比,我们结束了下面的伪代码,真正开车回家的是,除了步骤1-5,yield并且send是相同的,双方硬币管:
right_end.push(None) # the first half of g.send; sending None is what starts a generatorright_end.pause()left_end.start()initial_value = left_end.pull()if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")left_end.do_stuff()left_end.push(y) # the first half of yieldleft_end.pause()right_end.resume()value1 = right_end.pull() # the second half of g.sendright_end.do_stuff()right_end.push(value2) # the first half of g.send (again, but with a different value)right_end.pause()left_end.resume()x = left_end.pull() # the second half of yieldgoto 6关键的转换是我们将x = yield y和value1 = g.send(value2)每个拆分为两个语句:left_end.push(y)和x = left_end.pull(); 和value1 = right_end.pull()和right_end.push(value2)。yield关键字有两种特殊情况:x = yield和yield y。它们分别是 forx = yield None和 的语法糖_ = yield y # discarding value。
有关通过管道发送值的精确顺序的具体细节,请参见下文。
下面是上述内容的一个相当长的具体模型。首先,首先应该注意的是,对于任何生成器g,next(g)完全等价于g.send(None)。考虑到这一点,我们可以只关注如何send工作,只讨论使用send.
假设我们有
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
Run Code Online (Sandbox Code Playgroud)
现在,f粗略地将 desugars定义为以下普通(非生成器)函数:
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
Run Code Online (Sandbox Code Playgroud)
在这种转换中发生了以下情况f:
left_end可以被嵌套函数right_end访问,并且可以被外部作用域返回和访问——right_end这就是我们所知道的生成器对象。left_end.pull()是None,在消费过程中推值。x = yield y已被替换为两行:left_end.push(y)和x = left_end.pull()。sendfor 函数right_end,它与我们x = yield y在上一步中替换语句的两行相对应。在这个函数返回后可以继续的幻想世界中,g被分配right_end然后impl()被调用。所以在我们上面的例子中,如果我们一行一行地执行,大概会发生以下情况:
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
Run Code Online (Sandbox Code Playgroud)
这完全映射到上面的 16 步伪代码。
还有一些其他细节,比如错误是如何传播的,以及当你到达生成器的末端(管道关闭)时会发生什么,但这应该清楚地说明基本控制流在send使用时是如何工作的。
使用这些相同的脱糖规则,让我们看两个特殊情况:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,它们的脱糖方式与 相同f,唯一的区别是yield语句的转换方式:
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
Run Code Online (Sandbox Code Playgroud)
在第一个中,传递给的值f1最初被推送(产生),然后所有被拉动(发送)的值被立即推送(产生)。在第二个中,x当它第一次出现时没有价值(还)push,所以 anUnboundLocalError被提高。
这些也让我很困惑。这是我在尝试设置一个生成器时制作的一个示例,该生成器以交替顺序生成和接受信号(生成,接受,生成,接受)...
def echo_sound():
thing_to_say = '<Sound of wind on cliffs>'
while True:
thing_to_say = (yield thing_to_say)
thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
yield None # This is the return value of send.
gen = echo_sound()
print 'You are lost in the wilderness, calling for help.'
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
Run Code Online (Sandbox Code Playgroud)
输出是:
You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
Run Code Online (Sandbox Code Playgroud)
itr.send(None)和你正在做的事情是一样的next(itr),就是给出生成器中由yield给出的值。
下面的示例清楚地展示了这一点,以及如何更实际地使用它。
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
num = iterator_towards()
for i in num:
print(i)
if i == 5:
num.send(0)
Run Code Online (Sandbox Code Playgroud)
这将打印:
0
1
2
3
4
5
3
2
1
0
Run Code Online (Sandbox Code Playgroud)
处的代码i == 5告诉它发送0。这不在Noneiterator_towards 中,因此它改变了 的值dest。然后我们迭代到0。
但请注意,值 5 之后没有值 4。这是因为 的本质.send(0)是生成该4值并且未打印该值。
如果我们添加 acontinue我们可以重新产生相同的值。
0
1
2
3
4
5
3
2
1
0
Run Code Online (Sandbox Code Playgroud)
这将允许您迭代列表,但也可以动态地动态向其发送新的目标值。