python 生成器中的代码何时停止执行?

When does the execution of the code in a python generator stop?

我试图通过构建一个行为类似于 'enumerate' 内置函数的生成器来理解 yield 语句的行为,但我发现不一致取决于我如何遍历它。

def enumerate(sequence, start=0):
n = start
for elem in sequence:
    print("Before the 'yield' statement in the generator, n = {}".format(n))
    yield n, elem
    n += 1
    print("After the 'yield' statement in the generator, n = {}".format(n))

我对生成器的理解是,一旦达到 yield 语句,代码的执行就会停止,它 returns 是一个值。这与我通过下面的脚本得到的结果相符。

a = 'foo'
b = enumerate(a)
n1,v1 = next(b)
print('n1 = {}, v1 = {}\n'.format(n1,v1))
n2,v2 = next(b)
print('n2 = {}, v2 = {}'.format(n2,v2))

在这种情况下,生成器似乎恰好在 yield 语句处停止,并在 n+=1 中使用第二个 'next' 语句恢复:

Before the 'yield' statement in the generator, n = 0
n1 = 0, v1 = f

After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n2 = 1, v2 = o

但是,如果我使用下面的 for 循环,生成器似乎不会在 yield 语句处停止。

for n,v in enumerate(a[0:1]):
    print('n = {}, v = {}'.format(n,v))

这是我得到的:

Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1

考虑评论进行编辑

我意识到我只遍历了一个元素,但我没想到会看到最后的 "After the 'yield' statement in the generator" 句子(即使我遍历所有元素也会出现。

print('\n\n')
for n,v in enumerate(a):
    print('n = {}, v = {}'.format(n,v))

Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n = 1, v = o
After the 'yield' statement in the generator, n = 2
Before the 'yield' statement in the generator, n = 2
n = 2, v = o
After the 'yield' statement in the generator, n = 3

为什么会这样?

答案在于了解 python 中的 for 循环的作用: 它获取对象的迭代器(即 iter())并继续直到引发 StopIteration 异常。 StopIteration 生成器的代码完成时抛出异常,这意味着获取存在函数的 return 语句(也可以是隐式的)。 这就是为什么它不会在 yield 处停止,它会一直询问下一个 yield,直到生成器完成。

这里的根本问题是你混淆了 仅通过观察就知道发电机何时会耗尽这一事实与 Python只能通过运行ning代码知道。当Python到达你认为是最后一个的yield时,它实际上并不知道它是最后一个。如果您的生成器看起来像这样怎么办:

def enumeratex(x, start=0):
    for elem in x:
        yield start, x
        start += 1
    yield start, None

在这里,出于无人知晓的原因,在主生成器循环之后 return 编辑了最终的 None 元素。 Python 在您

之前无法知道生成器已完成
  1. Return 来自生成器。
  2. 引发错误,在这种情况下一切都会停止。

在 Python 3.7 之前的版本中,生成器可以引发 StopIteration 以指示终止。事实上,return 语句等价于 raise StopIteration(如果 returning None)或 raise StopIteration(return_value).

因此,虽然您告诉 Python 结束生成器的确切方式取决于您,但您必须明确说明。 yield 本身不会结束生成器。

TL;DR

生成器循环中的所有代码将始终 运行,即使在生成最后一个值之后也是如此,因为 Python 只能通过实际执行所有代码。