(list | set | dict)包含yield表达式的理解不返回(list | set | dict)

时间:2014-01-07 14:05:15

标签: python generator

Python 3.3

我构建了这个稍微神秘的python 3.3:

>>> [(yield from (i, i + 1, i)) for i in range(5)]
<generator object <listcomp> at 0x0000008666D96900>
>>> list(_)
[0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4]

如果我在列表构造函数中使用生成器理解,我会得到不同的结果:

>>> list((yield from (i, i + 1, i)) for i in range(5))
[0, 1, 0, None, 1, 2, 1, None, 2, 3, 2, None, 3, 4, 3, None, 4, 5, 4, None]

为什么列表理解不返回列表?

Python 2.7

我可以在python 2中得到类似的奇怪效果(使用集合理解,因为列表推导具有奇数范围):

>>> {(yield i) for i in range(5)}
<generator object <setcomp> at 0x0000000004A06120>
>>> list(_)
[0, 1, 2, 3, 4, {None}]

使用生成器理解时:

>>> list((yield i) for i in range(5))
[0, None, 1, None, 2, None, 3, None, 4, None]

{None}来自哪里?

2 个答案:

答案 0 :(得分:4)

使用this作为参考:

Python 3解释

此:

values = [(yield from (i, i + 1, i)) for i in range(5)]

转换为Python 3.x中的以下内容:

def _tmpfunc(): 
    _tmp = [] 
    for x in range(5): 
        _tmp.append(yield from (i, i + 1, i)) 
    return _tmp 
values = _tmpfunc()

这导致values包含生成器

然后,该生成器将从每个(i, i + 1, i)得到,直到最终到达return语句。在python 3中,这将throw StopIteration(_tmp) - 但是,list构造函数会忽略此异常。


另一方面,这个:

list((yield from (i, i + 1, i)) for i in range(5))

转换为Python 3.x中的以下内容:

def _tmpfunc():
    for x in range(5): 
        yield (yield from (i, i + 1, i))

values = list(_tmpfunc())

这一次,每次yield from完成时,它都会评估为None,然后在其他值中yield

答案 1 :(得分:3)

List(set,dict)comprehensions转换为与生成器表达式不同的代码结构。让我们看一下集合理解:

def f():
    return {i for i in range(10)}

dis.dis(f.__code__.co_consts[1])
  2           0 BUILD_SET                0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                12 (to 21)
              9 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             15 SET_ADD                  2
             18 JUMP_ABSOLUTE            6
        >>   21 RETURN_VALUE        

与等效的生成器表达式进行比较:

def g():
    return (i for i in range(10))

dis.dis(g.__code__.co_consts[1])
  2           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        

您会注意到,在生成器表达式具有yield的位置,set comprehension将一个值直接存储到它正在构建的集合中。

这意味着如果将yield表达式添加到生成器表达式的主体中,则将其与该语言为生成器主体构造的yield无法区分对待;因此,每次迭代会得到两个(或更多)值。

但是,如果您将yield添加到列表(set,dict)理解中,那么理解将从构建列表(set,dict)的函数转换为执行yield的生成器然后语句返回构造的列表(set,dict)。集合理解结果中的{None}是根据None表达式评估的每个yield构建的集合。


最后,为什么Python 3.3不生成{None}? (请注意,以前版本的Python 3可以。)这是因为PEP 380(a.k.a。yield from支持)。在Python 3.3之前,生成器中的returnSyntaxError: 'return' with argument inside generator;因此,我们的yield理解正在利用未定义的行为,但RETURN_VALUE操作码的实际结果只是从生成器生成另一个(最终)值。在Python 3.3中,明确支持return value;一个RETURN_VALUE操作码导致StopIteration被引发,这样可以停止生成器而不会产生最终值。