Python 3:发送方法

时间:2012-09-28 09:58:13

标签: python python-3.x generator

我无法理解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. 为什么第一个结果字符串不能反映发生器内部发生的任何事情?
  4. 无论如何,你能以某种方式评论这些sendyield陈述吗?

5 个答案:

答案 0 :(得分:51)

在生成器中使用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

Dumb waiter

当服务器从客户处获得订单时,他们将垫放在愚蠢的服务员中,send将它放到厨房,然后等待孵化器播放:

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

厨师(他一直在等舱)拿起订单,准备菜肴,yield到餐馆,然后等待下一个订单:

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

服务员(一直在等待的人)将菜肴带给顾客并返回另一个订单等。

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

答案 1 :(得分:18)

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

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

在翻译级别: X = yield i 等于以下行(订购事项):

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

答案 2 :(得分:6)

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()方法接受的值被赋予我然后我命名为x。 相反,值i由yield statment返回到生成器,x由send()方法确定。一个语句同时做两件事。

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

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

  • 产量当前结果(最初为无)
  • 接收其他值(无论有人用作.send的参数)
  • 根据收到的值重新计算当前结果
  • 循环回来

答案 4 :(得分:0)

注意:
为简单起见是我的回答限于当发电机在每行至多1 yield的命令的情况。

<强> TL; DR:

  • .send() 方法:

    • 发送一个值的当前暂停的命令,但
    • 接收从一个值的下,迎面而来的悬浮命令
  • 发送值是“接收”的产量表达本身。
    这意味着表达yield 7

    • 的产率值7,但
    • 自己的值,的即值的(yield 7),可以是例如"hello"
      (圆括号通常是强制除了最简单的情况。)

详细地:

<强>前言

g = gen()是发电机迭代器的实例gen().

  • 该命令next(g)的行为的准确g.send(None)

    • 因此,在接下来的说明中,我将完全省略next()功能。
  • 发送的不 - None仅允许如果实例g悬浮在与yield命令的发言:
    enter image description here

    • 为什么?因为.send()方法发送的值的直接将悬浮产量表达(参见<强> 4 在点在“一步一步”部分波纹管)。

    所以发送之前未 - None的值,必须把发电机实例这样的暂停状态通过向它的 None 值。它可以是简单为g.send(None)

    enter image description here

  • 但是,仅仅前g变得悬浮,它产生的yield命令的值。这样得到的值成为.send()方法的返回值:

    enter image description here

    我们可能想的在以后的使用一个变量使用这个接收值的或保存它,这样,而不是先前的两个画面,让我们开始我们的旅程,这样的:


<强>一步一步:

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

    enter image description here

    这意味着,即在可变from_iterator_1将是串"first_from_iterator"

  2. 现在,得到它的第一个值之后,我们有g在暂停状态

    enter image description here

    ,其允许我们发送给g的东西的有用下,除了None - 例如数1

  3. 因此,让发送号码1genter image description here

  4. g悬浮在表达 yield "first_from_iterator"的值这个表达式(本身)将成为1

    (是的,yield "first_from_iterator"就是的表达,同样地a + b是。)

    回想一下,此时的值"first_from_iterator"是很久以前已经产生。

  5. 在实例g,则唤醒,以及 - 继而 - {的{1}}现在等待一个返回值。 enter image description here

  6. 在先前暂停,现在唤醒语句将被执行。
    (该悬浮液之前,它未进行,它仅产生一个值。enter image description here 在我们的简单情况下(在沃肯语句是g.send())仍然没有进行,但什么

    • 保存SENT值(yield "first_from_iterator")给一个变量为了在后面使用而不是

      1
    • 或与它进行更复杂的计算,而不是

      received_1 = yield "first_from_iterator"      
      

  7. 在所有随后的声明,result = math.cos((yield "first_from_iterator") * math.pi) # result: -1 ,将执行,但只到与g在它命令的下一个语句。

    enter image description here

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

    enter image description here

    地中断yield再次并唤醒等待g方法(通过提供其预期的 - 产生 - 返回值)。

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

    enter image description here

  10. 现在我们的在相同的情况如在点2 - 所以在故事将被重复 - 刚刚执行(下一个).send()方法之前

    注意:
    将重复的用相同的问题在上面的“前言”部分的最后一点 - 我们可能不想抛出了产生价值,所以不是命令

    .send()

    是更好地利用作为事

    g.send(1)                          # Not very appropriate
    

    (以及类似地在接下来的from_iterator_2 = g.send(1) # Saving the 2nd yielded value 命令)。


大图:

(第一g.send(2)(与该send参数)开始执行它的命令的生成器实例。)

enter image description here