3

我有一个名为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:我知道当我第一次调用该函数时,主体没有运行,只是产生了一个生成器。我不明白的一点是,如果我调用并将其分配给变量会发生什么变化?

4

2 回答 2

13

在您的第一个示例中,您每次都创建一个新的生成器:

x().next()

这会从顶部启动生成器,因此是第一条语句。当 时a == 3,引发异常,否则生成器只会产生并暂停

当你稍后分配你的生成器时,全局a开始于5,然后代码从它离开的地方继续,直到它结束或遇到另一个yield语句,然后结束。当生成器函数结束时,它会引发StopIteration.

让我们把它分解成几个步骤:

  1. a = 5.
  2. 您创建新的生成器并调用.next()它。执行以下代码:

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 4
    yield a
    

    生成器在最后一行暂停,并4产生。

  3. 您创建一个新的生成器并调用.next()它。a4在开始:

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 3
    yield a
    

    生成器在最后一行暂停,并3产生。

  4. 您创建一个新的生成器并调用.next()它。a3在开始:

    global a
    if a == 3:  # True
        raise Exception("Stop")
    

    引发异常。

  5. 你又设置a = 5了。

  6. 您创建一个新的生成器,在其中存储一个引用b并调用.next()它。执行以下代码:

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 4
    yield a
    

    生成器在最后一行暂停,并4产生。

  7. .next() 再次调用 .引用的同一个现有生成器b。代码在暂停点恢复。

    该函数此时没有更多代码,并返回。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
于 2013-09-10T11:35:54.090 回答
0

您还不太了解 yield 的工作原理。我认为这个例子可能会有所帮助:

>>> def a():
...    for x in range(5):
...        yield x
...
>>> a()
<generator object a at 0xb7f0a9b4>
>>> list(a())
[0, 1, 2, 3, 4]

您通常希望在循环中使用 yield,它具有非常独特的行为,即返回一个值,然后再恢复循环。

如果您的示例, x 总是返回一个只生成一个项目的生成器。在您的第一个示例中,您多次调用 x ,因此您得到多个结果。在您的第二个示例中,您将它分配给一个变量,您只调用它一次,因此您只会得到一个结果。

此外,您通常不应该像以前那样使用全局变量。

保罗

于 2013-09-10T11:39:52.033 回答