Python : 生成器中 send() 的行为

Python : Behaviour of send() in generators

我在 python 3 中尝试使用生成器并编写了这个相当人为的生成器:

def send_gen():
    print("    send_gen(): will yield 1")
    x = yield 1
    print("    send_gen(): sent in '{}'".format(x))
    # yield  # causes StopIteration when left out


gen = send_gen()
print("yielded {}".format(gen.__next__()))

print("running gen.send()")
gen.send("a string")

输出:

    send_gen(): will yield 1
yielded 1
running gen.send()
    send_gen(): sent in 'a string'
Traceback (most recent call last):
  File "gen_test.py", line 12, in <module>
    gen.send("a string")
StopIteration

所以 gen.__next__() 到达行 x = yield 1 并产生 1。我认为 x 会被分配给 None,然后 gen.send() 会寻找next yield语句因为x = yield 1是"used",然后得到一个StopIteration.

相反,似乎 发生的是 x 被发送 "a string",它被打印出来,然后 然后 python 尝试寻找下一个 yield 并得到 StopIteration.

所以我试试这个:

def send_gen():
    x = yield 1
    print("    send_gen(): sent in '{}'".format(x))


gen = send_gen()
print("yielded : {}".format(gen.send(None)))

输出:

yielded : 1

但是现在没有错误了。在将 x 分配给 None 之后,send() 似乎没有尝试寻找 next yield 语句。

为什么行为略有不同?这与我启动发电机的方式有关吗?

这里的关键区别在于,您在第一个示例中点击了生成器两次,但在第二个示例中您只点击了生成器一次

当你定义一个 coroutine,即你打算将参数发送到其中的生成器时,你必须通过前进到第一个 yield 来预先 'prime' 它陈述。只有这样你才能发送值。在第一个示例中,您通过在尝试 send.

之前调用 gen.__next__() 明确地完成了此操作

在第二个示例中,您还通过执行 gen.send(None) 启动它(请注意,发送 None 实际上等同于调用 gen.__next__()next(gen))。但是你没有尝试第二次发送值,所以在那种情况下没有 StopIteration 。生成器只是停在那里,在 yield 语句处暂停,等待您再次点击它,这也是您之后还没有看到打印的原因。

另一点需要注意的是,如果您在第二个示例中发送了 None 以外的任何内容,则会出现错误:

TypeError: can't send non-None value to a just-started generator

这就是我在 'priming' 协程中所说的。

行为没有不同;在第二个设置中,您从未超越生成器中的第一个 yield 表达式。请注意 StopIteration 而不是错误 ;这是正常行为,只要生成器结束就会触发预期的信号。在您的第二个示例中,您从未到达生成器的末尾。

每当生成器到达 yield 表达式时,执行会暂停 就在那里 ,表达式在恢复之前无法在生成器中生成任何内容。 gen.__next__()gen.send() 都将从该点恢复执行,yield 表达式生成 gen.send()None 传入的值。如果有帮助,您可以将 gen.__next__() 视为 gen.send(None)。这里要意识到的一件事是 gen.send()yield return 发送值 firstthen 生成器继续到下一个 yield.

因此,给定您的第一个示例生成器,会发生这种情况:

  1. gen = send_gen() 创建生成器对象。代码暂停在函数的最顶部,什么都不执行。

  2. 你要么叫gen.__next__()要么叫gen.send(None);生成器开始并执行直到第一个 yield 表达式:

    print("    send_gen(): will yield 1")
    yield 1
    

    现在执行暂停gen.__next__()gen.send(None) 现在调用 return 1yield 1 产生的值。因为生成器现在已暂停,所以 x = ... 赋值还不能发生!只有当发电机再次恢复时才会发生这种情况。

  3. 您在第一个示例中调用 gen.send("a string"),不要在第二个示例中调用 any。所以对于第一个例子,现在恢复生成器函数:

    x = <return value of the yield expression>  # 'a string' in this case
    print("    send_gen(): sent in '{}'".format(x))
    

    现在函数 结束 ,所以 StopIteration 被引发。

因为您从未在第二个示例中恢复生成器,所以未到达生成器的末尾并且没有引发 StopIteration 异常。

请注意,因为生成器从函数的顶部开始,所以在那个点上没有 yield 表达式到 return 无论你用 gen.send() 发送什么,所以第一个 gen.send() 值必须始终为 None 否则会引发异常。最好对生成器 'prime' 使用显式 gen.__next__()(或者更确切地说 next(gen) 函数调用),这样它就会在第一个 yield 表达式处暂停。

从技术上讲,这是一个协程而不是生成器:

  1. 当我们调用send_gen()时,协程对象被创建
  2. 当调用 gen.___next____() 时 [我们应该使用 next(gen) ],生成器函数被调用 returns 1 并阻塞
  3. 当gen.send("a string")被调用时,协程被唤醒并处理输入(这里打印"a string")
  4. 然后协程退出