何时生成器不是生成器?为何有时调用带yield的函数不返回生成器对象

4 投票
2 回答
1868 浏览
提问于 2025-04-17 11:07

我有一个函数 (f),里面用到了 yield。我还有一个列表 (E)
如果我尝试用 E += f(),那么 f() 不会返回一个生成器对象,而是直接运行这个函数,这样就会抛出一个异常,因为一些全局变量还没有准备好。

为了避免大家的评论,我知道 E += f() 是错误的,但我只是用这个例子来说明我的问题。如果我做对了,比如 E += [f()] 或者 E.append(f()),那么 f() 就会返回一个生成器对象,而 f() 里的代码不会被执行,直到调用这个生成器对象的 next 方法。

我的问题是,为什么在错误的情况下 f() 会被执行,抛出的异常为什么不是类似于“函数对象不可迭代”的错误。

2 个回答

6

好的,当我在输入这个问题的最后一行时,我想我明白了。

E += f() 是在把两个列表合并在一起,这意味着 f() 的结果必须是可以遍历的东西,而且在合并的时候会对这个可遍历的东西进行计算,也就是说会调用 f().next。

E += [f()] 是把生成器对象放在一个可遍历的对象(也就是列表)里面,实际上是这个外面的列表被计算。

E.append(f()) 不是在合并列表,而是直接在原来的列表里添加一个元素(如果这个元素有可遍历的特性,那也会被忽略),所以生成器对象不会被计算。

6

因为表达式 f() 调用了 f,而 E += f() 会一直运行这个生成器,直到它结束,并把它生成的值添加到 E 中。这并不是“错误”的做法,但确实需要一个能够正常工作的 f,并且它要能产生有限的值:

>>> E = [1,2,3]
>>> def f():
...  yield 4
...  yield 5
...
>>> E += f()
>>> E
[1, 2, 3, 4, 5]

相比之下,E += [f()] 会调用 f,但在执行到代码的第一行之前就会停止,因为此时解释器已经得到了一个足够完整的对象,也就是生成器对象,可以存储在单元素列表 [f()] 中。只有在你之后请求更多元素时,比如从 E[-1],代码才会执行到第一个 yield 的地方,然后就会抛出异常。

撰写回答