我有一个名为 x 的函数,它生成如下所示的生成器:
a = 5
def x():
global a
if a == 3:
raise Exception("Stop")
a = a - 1
yield a
然后在 python shell 中我这样调用该函数:
>>> print x().next()
>>> 4
>>> print x().next()
>>> 3
>>> print x().next()
>>> <python-input-112-f3c02bba26c8> in x()
2 global a
3 if a == 3:
----> 4 raise Exception
5 a = a - 1
6 yield a
Exception:
但是,当我调用该函数并将其分配给一个变量时,它的行为有所不同:
>>> a = 5
>>> b = x()
>>> print b.next()
>>> 4
>>> print b.next()
>>> ----> 1 b.next()
StopIteration:
这怎么可能?它不应该打印出 3 并在下一次迭代中引发 StopIteration 吗?
PS:我知道第一次调用函数的时候,body并没有运行,只是产生了一个generator。我不明白的一点是,如果我调用并将其分配给一个变量,会发生什么变化?
最佳答案
在您的第一个示例中,您每次都创建了一个新生成器:
x().next()
这会从顶部启动生成器,所以第一个语句。当 a == 3
时,会引发异常,否则生成器只会让步并暂停。
当您稍后分配生成器时,全局 a
从 5
开始,然后代码从它离开的地方继续直到它结束或遇到另一个 yield
语句,然后 ended。当生成器函数结束时,它会引发 StopIteration
。
让我们把它分解成几个步骤:
a = 5
。您创建新的生成器并对其调用
.next()
。执行以下代码:global a if a == 3: # False raise Exception("Stop") a = a - 1 # a is now 4 yield a
生成器在最后一行暂停,生成
4
。您创建一个新的生成器并在其上调用
.next()
。a
开头是4
:global a if a == 3: # False raise Exception("Stop") a = a - 1 # a is now 3 yield a
生成器在最后一行暂停,生成
3
。您创建一个新的生成器并在其上调用
.next()
。a
开头是3
:global a if a == 3: # True raise Exception("Stop")
引发异常。
您再次设置
a = 5
。您创建了一个新的生成器,将引用存储在
b
中并对其调用.next()
。执行以下代码:global a if a == 3: # False raise Exception("Stop") a = a - 1 # a is now 4 yield a
生成器在最后一行暂停,生成
4
。您对
b
引用的同一个现有生成器再次调用.next()
。代码在暂停点恢复。该函数此时没有更多代码,并返回。
StopIteration
已引发。
如果您改用循环,您会更清楚地看出区别:
>>> def looping(stop):
... for i in looping(stop):
... yield i
...
>>> looping(3).next()
0
>>> looping(3).next()
0
注意每次我创建一个新的生成器时,循环是如何从头开始的。然而,存储一个引用,你会注意到它继续:
>>> stored = looping(3)
>>> stored.next()
0
>>> stored.next()
1
>>> stored.next()
2
>>> stored.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
在循环中,每执行一次yield
表达式,代码就会暂停;调用 .next()
继续上一次离开的函数。
StopIteration
异常是完全正常的;这就是生成器传达它们已完成的方式。 for
循环查找此异常以结束循环:
>>> for i in looping(3):
... print i
...
0
1
2
关于python - Python 中的 "yield",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18717834/