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

时间:2016-05-03 06:25:05

标签: python python-3.x generator yield coroutine

我在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

相反,似乎发生了什么<{1}}被发送“一个字符串”,打印出来,然后然后python试图寻找下一个x并获得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()后,yield似乎没有尝试查找下一个 x语句。

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

3 个答案:

答案 0 :(得分:5)

行为不同;在第二个设置中,您从未超越生成器中的第一个yield表达式。请注意,StopIteration 不是错误;这是正常行为,每当发电机结束时就会发出预期的信号。在你的第二个例子中,你从未到过生成器的末尾。

每当生成器到达yield表达式时,执行暂停 ,表达式就不能在生成器内生成任何内容,直到它恢复为止。 gen.__next__()gen.send()都将从该点恢复执行,yield表达式生成gen.send()传递的值或None。如果有帮助,您可以将gen.__next__()视为gen.send(None)。这里要认识到的一件事是gen.send() yield返回发送值第一然后生成器继续下一个{{ 1}}。

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

  1. yield创建生成器对象。代码暂停在函数的最顶层,没有执行任何操作。

  2. 您可以致电gen = send_gen()gen.__next__();生成器开始并执行,直到第一个gen.send(None)表达式:

    yield

    现在执行暂停print(" send_gen(): will yield 1") yield 1 gen.__next__()来电现在返回gen.send(None),即1产生的值。由于生成器现已暂停,yield 1作业尚无法完成!只有在再次恢复发电机时才会发生这种情况。

  3. 您在第一个示例中呼叫x = ...,在第二个示例中不要进行任何呼叫。因此,对于第一个示例,现在恢复生成器函数:

    gen.send("a string")

    现在功能结束,因此会引发x = <return value of the yield expression> # 'a string' in this case print(" send_gen(): sent in '{}'".format(x))

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

    请注意,因为生成器从函数的顶部开始,所以此时没有StopIteration表达式返回您使用yield发送的任何内容,因此必须始终使用gen.send()值是gen.send()或引发异常。最好使用明确的None(或者更确切地说是gen.__next__()函数调用)来引导&#39;生成器,因此它将在第一个next(gen)表达式暂停。

答案 1 :(得分:3)

这里的关键区别是你在第一个例子两次中击中了生成器,但是你在第二个例子中只用一次击中了生成器。

当你定义一个 coroutine ,即你想要发送参数的生成器时,你必须事先通过推进到第一个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

这就是我所说的“启动”协程。

答案 2 :(得分:0)

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

  1. 当我们调用send_gen()时,将创建协同例程对象
  2. 调用gen .___ next ____()[我们应使用next(gen)]时,将调用生成器函数,该函数将返回1并阻塞
  3. 调用gen.send(“ a string”)时,协程唤醒并处理了输入(此处打印为“ astring”)
  4. 然后退出例行程序