python生成器“发送”函数的目的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19302530/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
python generator "send" function purpose?
提问by Tommy
Can someone give me an example of why the "send" function associated with Python generator function exists? I fully understand the yield function. However, the send function is confusing to me. The documentation on this method is convoluted:
有人能给我一个例子,说明为什么存在与 Python 生成器函数相关的“发送”函数?我完全理解屈服函数。但是,发送功能让我感到困惑。关于此方法的文档很复杂:
generator.send(value)
Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.
恢复执行并将值“发送”到生成器函数中。value 参数成为当前 yield 表达式的结果。send() 方法返回生成器生成的下一个值,或者如果生成器退出而没有生成另一个值,则引发 StopIteration。
What does that mean? I thought value was the input to the function? The phrase "The send() method returns the next value yielded by the generator" seems to be also the exact purpose of the yield function; yield returns the next value yielded by the generator...
这意味着什么?我认为值是函数的输入?“send() 方法返回由生成器产生的下一个值”这句话似乎也是 yield 函数的确切目的;yield 返回生成器生成的下一个值...
Can someone give me an example of a generator utilizing send that accomplishes something yield cannot?
有人能给我一个使用 send 的生成器的例子吗?
采纳答案by Claudiu
It's used to send values into a generator that just yielded. Here is an artificial (non-useful) explanatory example:
它用于将值发送到刚刚产生的生成器中。这是一个人为的(无用的)解释性示例:
>>> 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
You can't do this just with yield
.
你不能只用yield
.
As to why it's useful, one of the best use cases I've seen is Twisted's @defer.inlineCallbacks
. Essentially it allows you to write a function like this:
至于它为什么有用,我见过的最好的用例之一是 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:
发生的事情是takesTwoSeconds()
返回 a Deferred
,这是一个承诺稍后计算值的值。Twisted 可以在另一个线程中运行计算。计算完成后,它将其传递给 deferred,然后将值发送回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
It's a lot more convoluted and unwieldy.
它更加复杂和笨拙。
回答by Jochen Ritzel
The send
method implements coroutines.
该send
方法实现了协程。
If you haven't encountered Coroutines they are tricky to wrap your head around because they change the way a program flows. You can read a good tutorialfor more details.
如果您还没有接触过协程,它们很难让您理解,因为它们改变了程序的流程。您可以阅读一个很好的教程以获取更多详细信息。
回答by Daniel Gratzer
This function is to write coroutines
这个功能是写协程
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
prints
印刷
From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...
See how the control is being passed back and forth? Those are coroutines. They can be used for all kinds of cool things like asynch IO and similar.
看看控制是如何来回传递的?那些是协程。它们可以用于各种很酷的东西,比如异步 IO 和类似的东西。
Think of it like this, with a generator and no send, it's a one way street
可以这样想,有发电机但没有发送,这是一条单向街
========== yield ========
Generator | ------------> | User |
========== ========
But with send, it becomes a two way street
但是通过发送,它变成了一条双向街道
========== yield ========
Generator | ------------> | User |
========== <------------ ========
send
Which opens up the door to the user customizing the generators behavior on the flyand the generator responding to the user.
这为用户即时定制生成器行为以及生成器响应用户打开了大门。
回答by radtek
This may help someone. Here is a generator that is unaffected by send function. It takes in the number parameter on instantiation and is unaffected by send:
这可能会帮助某人。这是一个不受发送函数影响的生成器。它在实例化时接受 number 参数并且不受发送的影响:
>>> 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
Now here is how you would do the same type of function using send, so on each iteration you can change the value of number:
现在,您将如何使用 send 执行相同类型的函数,因此在每次迭代中您都可以更改 number 的值:
def double_number(number):
while True:
number *= 2
number = yield number
Here is what that looks like, as you can see sending a new value for number changes the outcome:
如下所示,您可以看到为 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
You can also put this in a for loop as such:
你也可以把它放在一个 for 循环中:
for x in range(10):
n = c.send(n)
print n
For more help check out this great tutorial.
如需更多帮助,请查看这个很棒的教程。
回答by Jan Vlcinsky
Some use cases for using generator and send()
使用生成器和的一些用例 send()
Generators with send()
allow:
send()
允许的发电机:
- remembering internal state of the execution
- what step we are at
- what is current status of our data
- returning sequence of values
- receiving sequence of inputs
- 记住执行的内部状态
- 我们在哪一步
- 我们数据的当前状态是什么
- 返回值序列
- 接收输入序列
Here are some use cases:
以下是一些用例:
Watched attempt to follow a recipe
观看了遵循食谱的尝试
Let us have a recipe, which expects predefined set of inputs in some order.
让我们有一个配方,它需要按某种顺序预定义的一组输入。
We may:
我们可能会:
- 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
从配方创建一个实例- 让它得到一些输入
- 每个输入都返回有关当前锅中物品的信息
每次输入检查,输入是预期的(如果不是,则失败)
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
To use it, first create the watched_attempt
instance:
要使用它,首先创建watched_attempt
实例:
>>> watched_attempt = recipe()
>>> watched_attempt.next()
[]
The call to .next()
is necessary to start execution of the generator.
.next()
需要调用 to来启动生成器的执行。
Returned value shows, our pot is currently empty.
返回值显示,我们的锅目前是空的。
Now do few actions following what the recipe expects:
现在按照食谱的要求做一些动作:
>>> 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"))
[]
As we see, the pot is finally empty.
正如我们所见,锅终于是空的。
In case, one would not follow the recipe, it would fail (what could be desired outcome of watched attempt to cook something - just learning we did not pay enough attention when given instructions.
万一,一个人不遵循食谱,它就会失败(观看尝试做饭的预期结果可能是什么 - 只是了解到我们在给出指示时没有给予足够的关注。
>>> 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:
Notice, that:
请注意:
- there is linear sequence of expected steps
- the steps may differ (some are removing, some are adding to the pot)
- we manage to do all that by a functions/generator - no need to use complex class or similar strutures.
- 存在预期步骤的线性序列
- 步骤可能不同(有些正在移除,有些正在添加到锅中)
- 我们设法通过函数/生成器来完成所有这些 - 无需使用复杂的类或类似的结构。
Running totals
运行总计
We may use the generator to keep track of running total of values sent to it.
我们可以使用生成器来跟踪发送给它的值的运行总数。
Any time we add a number, count of inputs and total sum is returned (valid for the moment previous input was send into it).
每当我们添加一个数字时,都会返回输入计数和总和(在前一个输入被发送到它的那一刻有效)。
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)
The output would look like:
输出将如下所示:
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)
回答by Peter
These confused me too. Here is an example I made when trying to set up a generator which yields and accepts signals in alternating order (yield, accept, yield, accept)...
这些也让我很困惑。这是我在尝试设置生成器时制作的示例,该生成器以交替顺序(yield、accept、yield、accept)产生和接受信号...
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)
The output is:
输出是:
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!"
回答by user2059857
The send()
method controls what the value to the left of the yield expression will be.
该send()
方法控制 yield 表达式左侧的值。
To understand how yield differs and what value it holds, lets first quickly refresh on the order python code is evaluated.
要了解 yield 的不同之处以及它持有的价值,让我们首先快速刷新 Python 代码的评估顺序。
Python evaluates expressions from left to right. Notice that while evaluating an assignment, the right-hand side is evaluated before the left-hand side.
Python 从左到右计算表达式。请注意,在评估赋值时,右侧先于左侧进行评估。
So an expression a = b
the right hand side is evaluated first.
因此,a = b
首先评估右侧的表达式。
As the following demonstrates that a[p('left')] = p('right')
the right hand side is evaluated first.
如下所示,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]
What does yield do?, yield, suspends execution of the function and returns to the caller, and resumes execution at the same place it left off prior to suspending.
yield 做什么?,yield,挂起函数的执行并返回到调用者,并在挂起之前停止的同一位置继续执行。
Where exactly is execution suspended? You might have guessed it already...
the execution is suspended between the right and left side of the yield expression.So new_val = yield old_val
the execution is halted at the =
sign, and the value on the right (which is before suspending, and is also the value returned to the caller) may be something different then the value on the left (which is the value being assigned after resuming execution).
究竟在哪里暂停执行?您可能已经猜到了……在 yield 表达式的右侧和左侧之间暂停执行。所以new_val = yield old_val
执行在=
符号处停止,右边的值(挂起之前,也是返回给调用者的值)可能与左边的值(恢复后分配的值)有所不同执行)。
yield
yields 2 values, one to the right and another to the left.
yield
产生 2 个值,一个在右边,另一个在左边。
How do you control the value to the left hand side of the yield expression? via the .send()
method.
如何控制 yield 表达式左侧的值?通过.send()
方法。
The value of the yield expression after resuming depends on the method which resumed the execution. If
__next__()
is used (typically via either a for or thenext()
builtin) then the result is None. Otherwise, ifsend()
is used, then the result will be the value passed in to that method.
恢复后 yield 表达式的值取决于恢复执行的方法。如果
__next__()
使用(通常通过 for 或next()
内置函数),则结果为 None。否则,如果send()
使用,则结果将是传递给该方法的值。
回答by BallpointBen
The word "yield" has two meanings: to produce something (e.g., to yield corn), and to halt to let someone/thing else continue (e.g., cars yielding to pedestrians). Both definitions apply to Python's yield
keyword; what makes generator functions special is that unlike in regular functions, values can be "returned" to the caller while merely pausing, not terminating, a generator function.
“产量”这个词有两个含义:生产某物(例如,生产玉米),以及停下来让其他人/事物继续(例如,汽车让行人)。这两个定义都适用于 Python 的yield
关键字;生成器函数的特殊之处在于,与常规函数不同,值可以“返回”给调用者,而只是暂停而不是终止生成器函数。
It is easiest to imagine a generator as one end of a bidirectional pipe with a "left" end and a "right" end; this pipe is the medium over which values are sent between the generator itself and the generator function's body. Each end of the pipe has two operations: push
, which sends a value and blocks until the other end of the pipe pulls the value, and returns nothing; and pull
, which blocks until the other end of the pipe pushes a value, and returns the pushed value. At runtime, execution bounces back and forth between the contexts on either side of the pipe -- each side runs until it sends a value to the other side, at which point it halts, lets the other side run, and waits for a value in return, at which point the other side halts and it resumes. In other words, each end of the pipe runs from the moment it receives a value to the moment it sends a value.
最容易将生成器想象为具有“左”端和“右”端的双向管道的一端;这个管道是在生成器本身和生成器函数体之间发送值的媒介。管道的每一端都有两个操作:push
,发送一个值并阻塞直到管道的另一端拉取该值,并且不返回任何内容;和pull
,它会阻塞直到管道的另一端推送一个值,并返回推送的值。在运行时,执行在管道任一侧的上下文之间来回弹跳——每一侧都运行,直到它向另一侧发送一个值,此时它停止,让另一侧运行,并等待输入中的值返回,此时另一侧停止并继续。换句话说,管道的每一端从它接收到一个值到它发送一个值。
The pipe is functionally symmetric, but -- by convention I'm defining in this answer -- the left end is only available inside the generator function's body and is accessible via the yield
keyword, while the right end isthe generator and is accessible via the generator's send
function. As singular interfaces to their respective ends of the pipe, yield
and send
do double duty: they each both push and pull values to/from their ends of the pipe, yield
pushing rightward and pulling leftward while send
does the opposite. This double duty is the crux of the confusion surrounding the semantics of statements like x = yield y
. Breaking yield
and send
down into two explicit push/pull steps will make their semantics much more clear:
管道在功能上是对称的,但是 - 按照惯例,我在这个答案中定义 - 左端仅在生成器函数体内可用,可通过yield
关键字访问,而右端是生成器,可通过生成器的send
功能。作为它们各自管道末端的单一接口,yield
并send
执行双重任务:它们都向/从管道末端推送和拉动值,yield
向右推向左拉,而send
相反。这种双重职责是围绕诸如x = yield y
. 打破yield
并send
分为两个明确的推/拉措施将使它们的语义更加清晰:
- Suppose
g
is the generator.g.send
pushes a value leftward through the right end of the pipe. - Execution within the context of
g
pauses, allowing the generator function's body to run. - The value pushed by
g.send
is pulled leftward byyield
and received on the left end of the pipe. Inx = yield y
,x
is assigned to the pulled value. - Execution continues within the generator function's body until the next line containing
yield
is reached. yield
pushes a value rightward through the left end of the pipe, back up tog.send
. Inx = yield y
,y
is pushed rightward through the pipe.- Execution within the generator function's body pauses, allowing the outer scope to continue where it left off.
g.send
resumes and pulls the value and returns it to the user.- When
g.send
is next called, go back to Step 1.
- 假设
g
是发电机。g.send
通过管道的右端向左推动一个值。 - 在
g
暂停上下文中执行,允许生成器函数的主体运行。 - 推入的值
g.send
被向左拉,yield
并在管道的左端接收。在x = yield y
,x
分配给拉取值。 - 在生成器函数体内继续执行,直到
yield
到达包含的下一行。 yield
通过管道的左端向右推动一个值,回到g.send
。在x = yield y
,y
被向右推通过管道。- 生成器函数体内的执行暂停,允许外部作用域从它停止的地方继续。
g.send
恢复并拉取值并将其返回给用户。- 当
g.send
一次调用,回到第1步。
While cyclical, this procedure does have a beginning: when g.send(None)
-- which is what next(g)
is short for -- is first called (it is illegal to pass something other than None
to the first send
call). And it may have an end: when there are no more yield
statements to be reached in the generator function's body.
虽然是循环的,但这个过程确实有一个开始:当g.send(None)
——这是next(g)
它的缩写——第一次被调用(传递除None
第一次send
调用之外的其他东西是非法的)。它可能有一个结束:当yield
生成器函数的主体中没有更多的语句要到达时。
Do you see what makes the yield
statement (or more accurately, generators) so special? Unlike the measly return
keyword, yield
is able to pass values to its caller and receive values from its caller all without terminating the function it lives in! (Of course, if you do wish to terminate a function -- or a generator -- it's handy to have the return
keyword as well.) When a yield
statement is encountered, the generator function merely pauses, and then picks back up right where it left off upon being sent another value. And send
is just the interface for communicating with the inside of a generator function from outside it.
你看到是什么让yield
语句(或更准确地说,生成器)如此特别吗?与 measlyreturn
关键字不同,yield
它能够将值传递给其调用者并从其调用者接收值,而无需终止其所在的函数!(当然,如果您确实希望终止一个函数——或者一个生成器——使用return
关键字也很方便。)当yield
遇到一个语句时,生成器函数只是暂停,然后从它离开的地方重新开始在发送另一个值时关闭。并且send
只是从外部与生成器函数内部进行通信的接口。
If we really want to break this push/pull/pipe analogy down as far as we can, we end up with the following pseudocode that really drives home that, aside from steps 1-5, yield
and send
are two sides of the same coinpipe:
如果我们真的要下来,只要我们可以打破这种推/拉/管类比,我们结束了下面的伪代码,真正开车回家的是,除了步骤1-5,yield
并且send
是相同的,双方硬币管:
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_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 yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_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 yield
goto 6
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_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 yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_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 yield
goto 6
The key transformation is that we have split x = yield y
and value1 = g.send(value2)
each into two statements: left_end.push(y)
and x = left_end.pull()
; and value1 = right_end.pull()
and right_end.push(value2)
. There are two special cases of the yield
keyword: x = yield
and yield y
. These are syntactic sugar, respectively, for x = yield None
and _ = yield y # discarding value
.
关键的转换是我们将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
。
For specific details regarding the precise order in which values are sent through the pipe, see below.
有关通过管道发送值的精确顺序的具体细节,请参见下文。
What follows is a rather long concrete model of the above. First, it should first be noted that for any generator g
, next(g)
is exactly equivalent to g.send(None)
. With this in mind we can focus only on how send
works and talk only about advancing the generator with send
.
下面是上述内容的一个相当长的具体模型。首先,首先要注意的是,对于任何生成器g
,next(g)
完全等价于g.send(None)
。考虑到这一点,我们可以只关注如何send
工作,只讨论使用send
.
Suppose we have
假设我们有
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
Now, the definition of f
roughly desugars to the following ordinary (non-generator) function:
现在,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()
The following has happened in this transformation of f
:
在这种转换中发生了以下情况f
:
- We've moved the implementation into a nested function.
- We've created a bidirectional pipe whose
left_end
will be accessed by the nested function and whoseright_end
will be returned and accessed by the outer scope --right_end
is what we know as the generator object. - Within the nested function, the very first thing we do is check that
left_end.pull()
isNone
, consuming a pushed value in the process. - Within the nested function, the statement
x = yield y
has been replaced by two lines:left_end.push(y)
andx = left_end.pull()
. - We've defined the
send
function forright_end
, which is the counterpart to the two lines we replaced thex = yield y
statement with in the previous step.
- 我们已将实现移动到嵌套函数中。
- 我们已经创建了一个双向管道,它
left_end
会被嵌套函数访问,而它right_end
会被外部作用域返回和访问——right_end
这就是我们所知的生成器对象。 - 在嵌套函数中,我们做的第一件事就是检查
left_end.pull()
是None
,在消费过程中推值。 - 在嵌套函数中,该语句
x = yield y
已被替换为两行:left_end.push(y)
和x = left_end.pull()
。 - 我们已经定义了
send
for 函数right_end
,它与我们x = yield y
在上一步中替换语句的两行相对应。
In this fantasy world where functions can continue after returning, g
is assigned right_end
and then impl()
is called. So in our example above, were we to follow execution line by line, what would happen is roughly the following:
在这个函数返回后可以继续的幻想世界中,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
This maps exactly to the 16-step pseudocode above.
这完全映射到上面的 16 步伪代码。
There are some other details, like how errors are propagated and what happens when you reach the end of the generator (the pipe is closed), but this should make clear how the basic control flow works when send
is used.
还有一些其他细节,比如错误是如何传播的,以及当你到达生成器的末端(管道关闭)时会发生什么,但这应该清楚地说明基本控制流在send
使用时是如何工作的。
Using these same desugaring rules, let's look at two special cases:
使用这些相同的脱糖规则,让我们看两个特殊情况:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
For the most part they desugar the same way as f
, the only differences are how the yield
statements are transformed:
在大多数情况下,它们的脱糖方式与 相同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
In the first, the value passed to f1
is pushed (yielded) initially, and then all values pulled (sent) are pushed (yielded) right back. In the second, x
has no value (yet) when it first come times to push
, so an UnboundLocalError
is raised.
在第一个中,传递给的值f1
最初被推送(产生),然后所有被拉动(发送)的值被立即推送(产生)。在第二个中,x
当它第一次出现时没有价值(还)push
,所以 anUnboundLocalError
被提高。