Python中的"yield"

ozg*_*gur 3 python iteration yield generator

我有一个名为x的函数,它生成一个这样的生成器:

a = 5
def x():
    global a
    if a == 3:
        raise Exception("Stop")
    a = a - 1
    yield a
Run Code Online (Sandbox Code Playgroud)

然后在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:
Run Code Online (Sandbox Code Playgroud)

但是,当我调用该函数并将其分配给变量时,它的行为有所不同:

>>> a = 5
>>> b = x()
>>> print b.next()
>>> 4
>>> print b.next()
>>> ----> 1 b.next()
    StopIteration:
Run Code Online (Sandbox Code Playgroud)

这怎么可能呢?它不应该打印3并在下一次迭代中提高StopIteration吗?

PS:我知道当我第一次调用函数时,身体不会运行,只需生成一个生成器.我不明白的一点是,如果我调用并将其分配给变量会发生什么变化?

Mar*_*ers 13

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

x().next()
Run Code Online (Sandbox Code Playgroud)

从顶部开始生成器,所以第一个语句.何时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
    
    Run Code Online (Sandbox Code Playgroud)

    生成器在最后一行暂停,并被4放弃.

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

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 3
    yield a
    
    Run Code Online (Sandbox Code Playgroud)

    生成器在最后一行暂停,并被3放弃.

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

    global a
    if a == 3:  # True
        raise Exception("Stop")
    
    Run Code Online (Sandbox Code Playgroud)

    提出了一个例外.

  5. a = 5再次设置.

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

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

    生成器在最后一行暂停,并被4放弃.

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

    该函数在该点没有更多代码,并返回.StopIteration被提出来了.

如果您使用循环代替,您会看到更好的差异:

>>> def looping(stop):
...    for i in looping(stop):
...        yield i
...
>>> looping(3).next()
0
>>> looping(3).next()
0
Run Code Online (Sandbox Code Playgroud)

请注意每次创建新生成器时,循环从头开始.然而,存储引用,你会发现它继续:

>>> 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
Run Code Online (Sandbox Code Playgroud)

在循环期间,每次yield执行表达式时,代码都会暂停 ; 调用.next()继续前一次离开的功能.

StopIteration例外是完全正常的; 它是生成器如何沟通它们已完成的.一个for循环查找该异常来结束循环:

>>> for i in looping(3):
...     print i
... 
0
1
2
Run Code Online (Sandbox Code Playgroud)