【问题标题】:"yield" in PythonPython中的“产量”
【发布时间】:2013-09-14 02:22:41
【问题描述】:

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

【问题讨论】:

    标签: python iteration generator yield


    【解决方案1】:

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

    x().next()
    

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

    当您稍后分配生成器时,全局 a5 开始,然后代码继续从它离开的地方直到它结束或遇到另一个 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()a 开头是4

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

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

    4. 您创建一个新生成器并在其上调用.next()a 开头是3

      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. 您在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
    

    【讨论】:

    • 天哪,你怎么能在 3 分钟内写完所有这些文字?
    • @Matthias:我是一个快速打字员,但您也可以获得长达 5 分钟的宽限时间,以便在不更新上次编辑时间的情况下添加到帖子中。
    【解决方案2】:

    您还不太了解 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 ,因此您得到多个结果。在您的第二个示例中,您将它分配给一个变量,您只调用它一次,因此您只会得到一个结果。

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

    保罗

    【讨论】:

      猜你喜欢
      • 2016-06-08
      • 2011-09-17
      • 2021-02-23
      • 2015-11-14
      • 2018-02-04
      • 2017-04-17
      • 2014-06-16
      • 1970-01-01
      • 2015-08-17
      相关资源
      最近更新 更多