【问题标题】:Python 3: send method of generatorsPython 3:生成器的发送方法
【发布时间】:2012-09-20 04:57:05
【问题描述】:

我无法理解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 的调用者。 好吧,这似乎没有发生。为什么我们可以在gen()内部执行if语句和print函数。
  2. 我如何理解为什么X内外功能不同? 好的。让我们假设send(77) 将 77 传输到m。好吧,yield 表达式变为 77。 那么X = yield i是什么?而函数内部的 77 在外部发生时如何转换为 5?
  3. 为什么第一个结果字符串没有反映生成器内部发生的任何事情?

无论如何,您能以某种方式评论这些sendyield 声明吗?

【问题讨论】:

    标签: python python-3.x generator


    【解决方案1】:

    当您在生成器中使用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 i 的结果,那么X 将是None

    作为一个比喻,考虑一个dumb waiter

    当服务员接到顾客的订单时,他们将垫子放在愚蠢的服务员send它到厨房,然后在舱口等待菜:

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

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

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

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

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

    【讨论】:

    【解决方案2】:

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

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

    在解释器级别: X = yield i 等于下面几行(ORDER MATERS):

    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
    

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

    【讨论】:

    • 很好的回复!简单易懂的解释。您很好地确定了弱点X = yield i。会投票 10 次
    • “在词法层面:next() 等于 send(None)”。让我的一周更轻松。
    • 这真的很有帮助!
    • @user2297550 因为我们只能在生成器执行到yield 语句时才能为生成器内的变量赋值,所以我们需要调用g.send(None)next(g)首先。在实践中,将一个值发送到除None 之外的生成器而不执行生成器直到yield 语句,将导致TypeError
    • @user2297550 不,第一次调用g.send(None) 包括执行第一个yield 语句。换句话说,只有在第一个yield 语句执行后才允许向生成器发送值。我知道发送值g.send(v) 正在替换一个yield 语句,例如y = yield x 的rhs (yield x)。我认为以下问题的答案将有助于您理解。 stackoverflow.com/q/19892204/5277458
    【解决方案3】:
    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() 方法分配。一个语句同时做两件事。

    【讨论】:

      【解决方案4】:

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

      TL;DR:

      • .send() 方法:

        • 发送一个值到当前暂停yield命令(唤醒它),但是
        • 接收来自next, oncoming yield 命令的值。
      • .send() 方法发送的值的接收者是 yield 表达式本身。
        这意味着表达式yield 7

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

      大局:

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


      详细说明:

      前言:

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

      • next(g) 命令的行为g.send(None) 完全相同,所以你可以使用任何你喜欢的。

      • 仅当实例g 在使用yield 命令的语句处暂停时,才允许发送not-None
        @ 987654322@

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

        所以在发送 not-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. 所以让我们将号码1 发送到g

         

      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) 命令也是如此)。

      【讨论】:

        【解决方案5】:

        由于您甚至要求 cmets,请考虑以下情况:

        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() 并得到一个生成器;调用初始 next(f) 开始运行生成器并使用初始 None 值,并在 yield 行处暂停。然后它将绑定的方法f.send 返回给它的调用者。从此时开始,每次调用这个绑定方法时,generator.value本地都会接收到绑定方法的参数,重新计算value,然后循环回yieldvalue的当前值,等待下一次.send 获取另一个值。

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

        • yield 当前结果(最初为 None)
        • 接收另一个值(任何人用作.send 的参数)
        • 根据接收值重新计算当前结果
        • 循环返回

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-28
          • 1970-01-01
          • 2016-09-26
          • 2012-11-22
          • 2013-11-22
          • 2014-09-24
          • 2016-12-16
          相关资源
          最近更新 更多