5

我试图通过构建一个行为类似于“枚举”内置函数的生成器来理解 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 语句,代码的执行就会停止,并返回一个值。这与我在下面的脚本中得到的相符。

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 语句处停止,并在第二个“next”语句的 n+=1 中恢复:

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

编辑考虑评论

我意识到我只迭代了一个元素,但我没想到会看到最后一个“在生成器中的'yield'语句之后”句子(即使我迭代所有元素也会出现。

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

为什么会这样?

4

2 回答 2

3

这里的根本问题是,您混淆了仅通过查看生成器知道何时耗尽这一事实,而 Python 只能通过运行代码知道这一事实。当 Python 到达yield你认为是最后一个的时候,它实际上并不知道它是最后一个。如果你的生成器看起来像这样:

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

在这里,由于没有人知道的原因,None在主生成器循环之后返回了一个最终元素。Python 将无法知道生成器是否已完成,直到您

  1. 从发电机返回。
  2. 引发错误,在这种情况下,一切都会停止。

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

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

TL;博士

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

于 2018-06-18T16:06:40.593 回答
3

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

于 2018-06-18T14:37:43.967 回答