在列表parsing和生成器expression式中的产量

下面的行为对我来说似乎相当不合常理(Python 3.4):

>>> [(yield i) for i in range(3)] <generator object <listcomp> at 0x0245C148> >>> list([(yield i) for i in range(3)]) [0, 1, 2] >>> list((yield i) for i in range(3)) [0, None, 1, None, 2, None] 

最后一行的中间值实际上并不总是None ,它们是我们send到发生器的任何东西,相当于(我猜)到下面的发生器:

 def f(): for i in range(3): yield (yield i) 

这三条线完全可以工作,这让我觉得很有趣。 Reference指出yield只能在函数定义中允许(虽然我可能读错了,或者它可能只是从旧版本复制而来)。 前两行在Python 2.7中产生了一个SyntaxError ,但是第三行没有。

另外,这似乎很奇怪

  • 列表理解返回一个生成器而不是一个列表
  • 并且生成器expression式被转换为列表并且相应的列表理解包含不同的值。

有人可以提供更多信息吗?

生成器expression式,以及集合和词典理解被编译到(生成器)函数对象。 在Python 3中,列表parsing得到相同的处理; 它们本质上都是一个新的嵌套范围。

你可以看到这个,如果你试图反汇编一个生成器expression式:

 >>> dis.dis(compile("(i for i in range(3))", '', 'exec')) 1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>) 3 LOAD_CONST 1 ('<genexpr>') 6 MAKE_FUNCTION 0 9 LOAD_NAME 0 (range) 12 LOAD_CONST 2 (3) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 POP_TOP 23 LOAD_CONST 3 (None) 26 RETURN_VALUE >>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 11 (to 17) 6 STORE_FAST 1 (i) 9 LOAD_FAST 1 (i) 12 YIELD_VALUE 13 POP_TOP 14 JUMP_ABSOLUTE 3 >> 17 LOAD_CONST 0 (None) 20 RETURN_VALUE 

以上显示了一个生成器expression式被编译为一个代码对象,作为一个函数加载( MAKE_FUNCTION从代码对象中创build函数对象)。 .co_consts[0]引用让我们看到为expression式生成的代码对象,它使用YIELD_VALUE就像生成器函数一样。

因此, yieldexpression式就是在这种情况下工作的,因为编译器将这些看作是伪装的函数。

不过,我认为这是一个错误; yield在这些expression中没有地位。 Python 语法允许它(这就是为什么代码是可编译的),但yieldexpression式规范表明在这里使用yield不应该实际上工作:

yieldexpression式仅在定义一个生成器函数时使用,因此只能在函数定义的主体中使用。

这已经导致了一些令人困惑的错误 ,那就是有人试图在发生器函数中使用yield函数,在一个生成器expression式中,期望yield函数适用于函数。 Python开发者意识到这个问题,Guido在logging中指出这不是意图:

我认为它在3.x中的工作方式肯定是错误的。 (特别是因为它在2.x中按预期工作)

我同意Inyeol的select:(1)对于listcomps和genexps来说,它的工作是正确的;(2)如果这是不可能的话,禁止在genexp或listcomp中产出。

列表理解中的yield和发生器expression式中的yield之间的区别在于这两个expression式如何实现的差异。 在Python 3中,列表理解使用LIST_APPEND调用来将堆栈的顶部添加到正在构build的列表中,而生成器expression式LIST_APPEND生成该值。 join(yield <expr>)只是将另一个YIELD_VALUE操作码添加到:

 >>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0]) 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 13 (to 22) 9 STORE_FAST 1 (i) 12 LOAD_FAST 1 (i) 15 YIELD_VALUE 16 LIST_APPEND 2 19 JUMP_ABSOLUTE 6 >> 22 RETURN_VALUE >>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 12 (to 18) 6 STORE_FAST 1 (i) 9 LOAD_FAST 1 (i) 12 YIELD_VALUE 13 YIELD_VALUE 14 POP_TOP 15 JUMP_ABSOLUTE 3 >> 18 LOAD_CONST 0 (None) 21 RETURN_VALUE 

字节码索引15和12处的YIELD_VALUE操作码是额外的,巢中的杜鹃。 所以对于list-comprehension-turned-generator你有1个yield每次产生堆栈的顶端(用yield返回值replace堆栈的顶部),并且对于generator生成器expression式variables你产生栈顶整数),然后再次产生,但是现在堆栈包含了yield的返回值,而你第二次得到None

对于列表理解,仍然返回预期的list对象输出,但Python 3将其视为一个生成器,所以返回值作为value属性附加到StopIterationexception :

 >>> from itertools import islice >>> listgen = [(yield i) for i in range(3)] >>> list(islice(listgen, 3)) # avoid exhausting the generator [0, 1, 2] >>> try: ... next(listgen) ... except StopIteration as si: ... print(si.value) ... [None, None, None] 

那些None对象是yieldexpression式的返回值。

并再次重申这一点; 同样的问题也适用于Python 2和Python 3中的字典和集合理解; 在Python 2中, yield返回值仍然被添加到预期的字典或set对象中,并且返回值最后被“放弃”,而不是附加到StopIterationexception:

 >>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()}) ['bar', 'foo', 'eggs', 'spam', {None: None}] >>> list({(yield i) for i in range(3)}) [0, 1, 2, set([None])]