28

我看不懂send方法。我知道它是用来操作发电机的。但语法在这里:generator.send(value).

我不知何故无法理解为什么该值应该成为当前yield表达式的结果。我准备了一个例子:

def gen():
    for i in range(10):
        X = yield i
        if X == 'stop':
            break
        print("Inside the function " + str(X))

m = gen()
print("1 Outside the function " + str(next(m)) + '\n')
print("2 Outside the function " + str(next(m)) + '\n')
print("3 Outside the function " + str(next(m)) + '\n')
print("4 Outside the function " + str(next(m)) + '\n')
print('\n')
print("Outside the function " + str(m.send(None)) + '\n') # Start generator
print("Outside the function " + str(m.send(77)) + '\n')
print("Outside the function " + str(m.send(88)) + '\n')
#print("Outside the function " + str(m.send('stop')) + '\n')
print("Outside the function " + str(m.send(99)) + '\n')
print("Outside the function " + str(m.send(None)) + '\n')

结果是:

1 Outside the function 0

Inside the function None
2 Outside the function 1

Inside the function None
3 Outside the function 2

Inside the function None
4 Outside the function 3



Inside the function None
Outside the function 4

Inside the function 77
Outside the function 5

Inside the function 88
Outside the function 6

Inside the function 99
Outside the function 7

Inside the function None
Outside the function 8

好吧,坦率地说,这让我感到惊讶。

  1. 在文档中我们可以读到,当一个yield语句被执行时,生成器的状态被冻结并且 的值expression_list被返回给next的调用者。好吧,这似乎没有发生。为什么我们可以在里面执行if语句和print函数gen()
  2. 我如何理解为什么X函数内部和外部不同?好的。让我们假设将send(77)77 传输到m. 那么,yield表达式变为 77。那么 是什么X = yield i?函数内部的 77 在外部发生时如何转换为 5?
  3. 为什么第一个结果字符串没有反映生成器内部发生的任何事情?

无论如何,你能以某种方式评论这些sendyield陈述吗?

4

5 回答 5

62

当您在生成器中使用send和表达yield时,您将其视为协程;一个单独的执行线程,可以顺序交错运行,但不能与其调用者并行运行。

当调用者执行R = m.send(a)时,它将对象a放入生成器的输入槽,将控制权转移给生成器,并等待响应。生成器接收对象a作为 的结果X = yield i,并运行直到遇到另一个yield表达式,例如Y = yield j。然后它放入j它的输出槽,将控制权转移回调用者,并等待它再次恢复。调用者接收j到 的结果R = m.send(a),并运行直到它遇到另一个S = m.send(b)语句,依此类推。

R = next(m)R = m.send(None)和;一样 它被放入None生成器的输入槽,所以如果生成器检查X = yield ithen的结果X将是None.

作为一个比喻,考虑一个愚蠢的服务员

愚蠢的服务员

当服务员接到顾客的订单时,他们将垫子放在哑巴服务员的手上,send然后送到厨房,然后在舱口等待菜:

R = kitchen.send("Ham omelette, side salad")

厨师(一直在舱口等)拿起订单,准备好菜,yield送到餐厅,然后等待下一个订单:

next_order = yield [HamOmelette(), SideSalad()]

服务员(一直在舱口等待)把菜拿给顾客,然后带着另一个订单回来,等等。

因为服务员和厨师都在下send订单或yield菜后在舱口等待,所以任何时候只有一个人在做任何事情,即该过程是单线程的。双方都可以使用正常的控制流,因为生成器机器(哑巴服务员)负责交错执行。

于 2012-09-28T10:32:09.880 回答
29

最令人困惑的部分应该是这一行X = yield i,特别是当您调用send()生成器时。实际上,您唯一需要知道的是:

在词汇层面: next()等于send(None)

在解释器级别: X = yield i等于以下行(ORDER MATTERS):

yield i
# won't continue until next() or send() is called
# and this is also the entry point of next() or send()
X = the_input_of_send

send(None)并且,这 2 行注释是我们需要第一次调用的确切原因,因为生成器会在将值分配给之前i返回(yield i)X

于 2015-07-11T07:50:35.740 回答
7
def gen():
    i = 1
    while True:
        i += 1
        x = yield i
        print(x)

m = gen()
next(m)
next(m)
m.send(4)

结果

None
4

查看上面更简化的代码。
我认为导致您感到困惑的是'x = yield i'语句,这个语句并不是说从send()方法接受的值分配给i然后我分配给x。相反,值 i 由 yield 语句返回给生成器,x 由 send() 方法分配。一个语句同时做两件事。

于 2013-10-27T05:49:05.810 回答
3

注意:
为简单起见,我的回答仅限于生成器yield每行最多有 1 个命令的情况。

TL;博士:

  • .send()方法:

    • 向当前挂起的命令 发送一个值yield(唤醒它),但是
    • 从下一个即将到来 yield的命令接收一个值。
  • .send()方法发送的值的接收者是yield 表达式本身。
    这意味着表达式yield 7

    • 产生价值7,但是
    • 例如,它自己的值,即 的值(yield 7),可以是 "hello"
      (括号通常是强制性的,除了最简单的情况)——如果这个yield 7命令是通过方法唤醒的.send("hello")

大图:

第一个send(带有None参数)启动生成器实例,因此它开始执行其命令。

在此处输入图像描述


详细地:

前言:

Let g = gen(), ieg是生成器迭代器的一个实例gen()
(下图的右侧)。

  • 该命令的next(g)行为与 完全相同 g.send(None),因此您可以使用任何您喜欢的命令。

  • 仅当实例在使用以下命令的语句处暂停时才允许发送None值:gyield
    在此处输入图像描述

    • 为什么?因为该.send()方法可能只向等待(暂停)yield表达式发送一个值(请参阅下面“逐步”部分中的第4点)。

    因此,在发送非值之前,我们必须通过发送None值将生成器实例置于这种挂起状态None。它可能很简单g.send(None)

    在此处输入图像描述

  • 但就在g被挂起之前,它产生了yield命令的值。这个产生的值成为.send()方法的返回值:

    在此处输入图像描述

    我们可能想要使用这个接收到的值或者将它保存在一个变量中以供以后使用,所以我们先用下面的两张图片代替之前的两张图片:


一步步:

  1. 第一个.send()启动实例g。实例g开始执行它的命令直到第一yield条语句,它产生它的值:

    在此处输入图像描述

    这意味着,变量from_iterator_1中将是字符串"first_from_iterator"

     

  2. 现在,在产生它的第一个值之后,我们g处于暂停状态

    在此处输入图像描述

    这允许我们发送g一些有用的东西,而不是None——例如数字1

     

  3. 因此,让我们将号码发送1g在此处输入图像描述

     

  4. 由于在表达式g处暂停,该表达式(本身)的值 将变为。 yield "first_from_iterator"1

    (是的,yield "first_from_iterator" 一个表达式,同样a + b是。)

    回想一下,此时该值"first_from_iterator"很久以前就已经产生了。

     

  5. 然后实例g被唤醒,并且 - 反过来 -g.send()现在等待返回值。 在此处输入图像描述

     

  6. 之前暂停,现在唤醒的语句将被执行。
    (在暂停之前,它没有被执行,它只产生一个值。在此处输入图像描述 在我们的简单情况下(唤醒语句是yield "first_from_iterator"

    • 将接收到的值 ( 1) 保存到变量中以供以后使用?

      received_1 = yield "first_from_iterator"      
      
    • 还是用它执行更复杂的计算?

      result = 3 * (yield "first_from_iterator") + 2           # result: 5
      

     

  7. g将执行所有后续语句,但仅执行到包含yield命令的下一条语句。

    在此处输入图像描述

     

  8. 下一条语句(其中包含yield命令)产生一个值

    在此处输入图像描述

    它再次挂起g,并唤醒等待.send()方法(通过为其提供等待的 - 产生的 - 返回值)。

     

  9. 它允许在它之后执行下一个命令:

    在此处输入图像描述

     

  10. 现在我们处于与第 2 点相同的情况。——就在执行(下一个).send()方法之前——所以故事将被重复

    注意:
    将重复与上面“前言”部分最后一点相同的问题——我们可能不想丢弃产生的值,所以而不是命令

    g.send(1)                          # Not very appropriate
    

    最好用一些东西作为

    from_iterator_2 = g.send(1)        # Saving the 2nd yielded value
    

    (对于下一个g.send(2)命令也是如此)。

于 2021-01-07T16:30:15.187 回答
0

由于您甚至要求发表评论,请考虑以下情况:

def lambda_maker():
    def generator():
        value = None
        while 1:
            value = yield value
            value= value[0][1]
    f = generator()
    next(f)  # skip the first None
    return f.send  # a handy lambda value: value[0][1]

现在以下两行是等价的:

a_list.sort(key=lambda a: a[0][1])
a_list.sort(key=lambda_maker())

(顺便说一下,在当前(2018-05-26,GDPR 后第 1 天☺)CPython2 和 CPython3 实现中,第二行的运行速度比第一行快,但这是与每个函数调用的框架对象初始化开销相关的细节。)

这里会发生什么?lambda_maker调用f=generator()并获取一个生成器;调用 initialnext(f)开始运行生成器并消耗初始值,并在该行None暂停。yield然后它将绑定的方法返回f.send给它的调用者。从此时开始,每次调用这个绑定方法,generator.value本地都会接收到绑定方法的参数,重新计算value然后循环返回yield当前值,value等待下一个.send获取另一个值。

生成器对象保留在内存中,它在循环中所做的只是:

  • 产生当前结果(最初无)
  • 接收另一个值(任何人用作参数.send
  • 根据接收值重新计算当前结果
  • 环回
于 2018-05-26T09:03:22.890 回答