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

6

我正在尝试使用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()将寻找下一个yield语句,因为x = yield 1已经“使用”了,然后得到StopIteration
相反,似乎发生的是x被发送了"一个字符串",它被打印出来,然后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

但是现在没有错误了。 send() 似乎没有尝试在将 x 赋值为 None 后查找下一个 yield 语句。

为什么行为略有不同?这与我如何启动生成器有关吗?

3个回答

6
行为并没有改变;在第二个设置中,您从未超越生成器中的第一个yield表达式。请注意,StopIteration不是错误;这是正常行为,每当生成器结束时发出的预期信号。在您的第二个示例中,您只是没有到达生成器的末尾。
每当生成器到达yield表达式时,执行就会暂停在那里,在生成器内部,表达式无法产生任何内容,直到它被恢复。gen.__next__()或gen.send()都将从该点恢复执行,并使yield表达式生成gen.send()传入的值或None。如果有帮助的话,您可以将gen.__next__()视为gen.send(None)。要注意的一件事是,gen.send()首先返回发送的值,然后生成器继续进行下一个yield。
因此,对于您的第一个示例生成器,发生以下情况:
  1. gen = send_gen() creates the generator object. The code is paused at the very top of the function, nothing is executed.

  2. You either call gen.__next__() or gen.send(None); the generator commences and executes until the first yield expression:

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

    and execution now pauses. The gen.__next__() or gen.send(None) calls now return 1, the value yielded by yield 1. Because the generator is now paused, the x = ... assignment can't yet take place! That'll only happen when the generator is resumed again.

  3. You call gen.send("a string") in your first example, don't make any call in the second. So for the first example, the generator function is resumed now:

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

    and now the function ends, so StopIteration is raised.

在第二个示例中,由于您从未恢复生成器,因此未到达生成器的末尾,也不会引发StopIteration异常。

请注意,由于生成器始于函数的顶部,在该点上没有yield表达式来返回您使用gen.send()发送的任何内容,因此第一个gen.send()值必须始终为None,否则将引发异常。最好使用显式的gen.__next__()(或者说是next(gen)函数调用)来“启动”这个生成器,使其在第一个yield表达式处暂停。


啊,我明白了。那些 x = ... 的解释帮了很多。谢谢! - peonicles
执行是在yield赋值之前还是之后暂停的?我认为在x = yield 1中,yield 1和赋值(=)是两个独立的步骤。如果我错了,请纠正我。 - nn0p
@nn0p,它们是两个独立的步骤。yield 1 暂停,直到生成器被恢复,在这一点上表达式结果被分配给 x - Martijn Pieters

3
关键的区别在于你在第一个示例中两次调用了生成器,而在第二个示例中只一次调用了生成器。
当定义一个协程时,即你想发送参数的生成器,你需要预先“激活”它,通过前进到第一个yield语句。 只有这样才能发送值。在第一个示例中,你明确地调用了gen.__next __(),然后尝试send
在第二个示例中,你也通过执行gen.send(None)来激活它(请注意,发送None 实际上等同于调用gen.__next__()next(gen))。但然后你没有尝试再次发送值,所以在那种情况下没有StopIteration。生成器只是停在那里暂停在yield语句处,等待你再次激活它,这也是为什么你还没有看到后面的打印输出。
另一个需要注意的是,如果你在第二个示例中发送了除None之外的任何内容,都会报错:
TypeError: can't send non-None value to a just-started generator

这就是我所说的“激活”协程的过程。

0

这实际上是一个协程而不是生成器:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接