Python 3:发送方法

Kif*_*sif 27 python generator python-3.x

我无法理解这种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')
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

嗯,坦率地说,这让我很惊讶.

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

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

eca*_*mur 53

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

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

傻服务员

当服务器从客户处获得订单时,他们将垫放在愚蠢的服务员,send将其放入厨房,然后等待孵化器进入菜肴:

R = kitchen.send("Ham omelette, side salad")
Run Code Online (Sandbox Code Playgroud)

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

next_order = yield [HamOmelette(), SideSalad()]
Run Code Online (Sandbox Code Playgroud)

服务器(一直在等待孵化器)将菜肴带给顾客并返回另一个订单等.

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

  • 在编程中,"并行"意味着"并发",但相反的情况通常并非如此.[并发编程和并行编程之间的区别](http://stackoverflow.com/q/1897993/4279) (3认同)
  • 它同时运行,只是不并行。 (2认同)
  • 这个比喻很棒.让我的困惑消失了.谢谢. (2认同)

xxm*_*jia 20

最令人困惑的部分应该是这条线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
Run Code Online (Sandbox Code Playgroud)

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

  • 很棒的回复!简单易懂的解释。您很好地识别了弱点“X = yield i”。会赞成 10 次 (2认同)

Mar*_*anD 14

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

\n

长话短说:

\n
    \n
  • 方法.send()

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

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

大局:

\n

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

\n

在此输入图像描述

\n
\n

详细地:

\n

前言:

\n

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

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

    \n
  • \n
  • 仅当实例在使用以下命令的语句中挂起时才允许发送None \ngyield
    在此输入图像描述

    \n
      \n
    • 为什么?因为该方法可能只向等待(挂起)的表达式.send()发送值(请参见下面 \xe2\x80\x9cStep by step\xe2\x80\x9d 部分中的第4点)。yield
    • \n
    \n

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

    \n

    在此输入图像描述

    \n
  • \n
  • 但就在g暂停之前,它会产生命令的值yield。这个产生的值成为该方法的返回值.send()

    \n

    在此输入图像描述

    \n

    我们可能想使用这个接收到的值或将其保存在变量中以供以后使用,因此我们不使用前面的两张图片,而是以此开始我们的旅程:

    \n
  • \n
\n
\n

一步步:

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

    \n

    在此输入图像描述

    \n

    这意味着变量中将from_iterator_1是 string "first_from_iterator"

    \n

    \xc2\xa0

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

    \n

    在此输入图像描述

    \n

    这允许我们发送到除\xe2\x80\x94之外的有用的g东西,例如数字。None1

    \n

    \xc2\xa0

    \n
  4. \n
  5. 因此,让我们将号码发送1g:\n在此输入图像描述

    \n

    \xc2\xa0

    \n
  6. \n
  7. 由于在表达式g处挂起,\n该表达式(本身)的值 将变为。 yield "first_from_iterator"1

    \n

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

    \n

    回想一下,此时此刻的价值"first_from_iterator"早就已经产生了。

    \n

    \xc2\xa0

    \n
  8. \n
  9. 然后实例g被唤醒,\xe2\x80\x94 依次 \xe2\x80\x94 现在g.send()等待返回值。\n在此输入图像描述

    \n

    \xc2\xa0

    \n
  10. \n
  11. 先前挂起的、现在唤醒的语句将被执行。
    \n(挂起之前,并没有执行,只是产生了一个值。 )\n在此输入图像描述\n在我们的简单情况下(唤醒语句是yield "first_from_iterator"),仍然没有任何需要执行的操作,但是呢

    \n\n

    \xc2\xa0

    \n
  12. \n
  13. 中的所有后续语句都g将被执行,但仅限于包含该yield命令的下一条语句。

    \n

    在此输入图像描述

    \n

    \xc2\xa0

    \n
  14. \n
  15. 下一条语句(其中包含yield命令)会产生一个值

    \n

    在此输入图像描述

    \n

    g再次挂起,并唤醒等待.send()方法(通过向其提供等待的 \xe2\x80\x94 产生 \xe2\x80\x94 返回值)。

    \n

    \xc2\xa0

    \n
  16. \n
  17. 它允许在其之后执行下一个命令:

    \n

    在此输入图像描述

    \n

    \xc2\xa0

    \n
  18. \n
  19. 现在我们处于与第 2 点相同的情况。 \xe2\x80\x94 就在执行(下一个).send()方法 \xe2\x80\x94 之前,因此故事将重复

    \n

    注意:
    \n它将重复与上面 \xe2\x80\x9cPreface\xe2\x80\x9d 部分的最后一点相同的问题\xe2\x80\x94 我们可能不想丢弃生成的结果值,所以代替命令

    \n
    g.send(1)                          # Not very appropriate\n
    Run Code Online (Sandbox Code Playgroud)\n

    最好使用一些东西作为

    \n
    from_iterator_2 = g.send(1)        # Saving the 2nd yielded value\n
    Run Code Online (Sandbox Code Playgroud)\n

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

    \n
  20. \n
\n


小智 6

def gen():
    i = 1
    while True:
        i += 1
        x = yield i
        print(x)

m = gen()
next(m)
next(m)
m.send(4)
Run Code Online (Sandbox Code Playgroud)

结果

None
4
Run Code Online (Sandbox Code Playgroud)

看看上面更简化的代码.
我认为引起你困惑的事情是'x = yield i'声明,这个声明并没有说从send()方法接受的值,然后我把它命名为x.相反,值i由yield statment返回到生成器,x由send()方法确定.一个语句同时做两件事.