我在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
语句。
为什么行为略有不同?这与我启动发电机的方式有关吗?
答案 0 :(得分:5)
行为不不同;在第二个设置中,您从未超越生成器中的第一个yield
表达式。请注意,StopIteration
不是错误;这是正常行为,每当发电机结束时就会发出预期的信号。在你的第二个例子中,你从未到过生成器的末尾。
每当生成器到达yield
表达式时,执行暂停 ,表达式就不能在生成器内生成任何内容,直到它恢复为止。 gen.__next__()
或gen.send()
都将从该点恢复执行,yield
表达式生成gen.send()
传递的值或None
。如果有帮助,您可以将gen.__next__()
视为gen.send(None)
。这里要认识到的一件事是gen.send()
yield
返回发送值第一,然后生成器继续下一个{{ 1}}。
因此,鉴于您的第一个示例生成器,会发生这种情况:
yield
创建生成器对象。代码暂停在函数的最顶层,没有执行任何操作。
您可以致电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
作业尚无法完成!只有在再次恢复发电机时才会发生这种情况。
您在第一个示例中呼叫x = ...
,在第二个示例中不要进行任何呼叫。因此,对于第一个示例,现在恢复生成器函数:
gen.send("a string")
现在功能结束,因此会引发x = <return value of the yield expression> # 'a string' in this case
print(" send_gen(): sent in '{}'".format(x))
。
因为您在第二个示例中从未恢复生成器,所以未到达生成器的末尾并且不会引发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)
从技术上讲,这是一个协同例程,而不是生成器: