包含yield表达式的(list|set|dict)推导式不会返回一个(list|set|dict)。

17

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}是从哪来的?


1
这似乎与编程有关,但我还没有完全理解它,无法自己解释。参考链接:https://groups.google.com/forum/#!topic/python-ideas/JOFw5Al-kEM - kojiro
yield 不是应该生成一个生成器吗?也就是说,不是生成一个集合,而是生成一个能够生成集合的结构体吗? - Oleg Sklyar
我觉得这似乎与Python是否正在构建函数(集合推导式或在Python 3中是任意推导式)或对象(Python 2中的列表推导式)有关。 - kojiro
使用 yield 在这里并不是必需的,直接用 [ j for i in range(5) for j in (i, i+1, i) ] 即可达到预期效果。 - Alfe
显示剩余3条评论
2个回答

4
使用这个作为参考:

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)中产生,直到最终到达返回语句。 在python 3中,这将引发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

是的。我认为在这个地方使用yield只会破坏概念;编译器应该拒绝它,因为它永远不会产生有用的结果。(但如果我错了,我很乐意接受纠正。) - Alfe
1
_tmp 变量是在 _tmpfunc() 中创建并返回的吗?或者它是在函数外部创建,传递给函数并原地修改,因此甚至不需要 return 语句? - Alfe
@Alfe:啊,我相信这可能是Python 2和3之间的区别 - 3肯定会返回,因为有一个StopIteration - Eric

3

List(set,dict)的推导式与生成器表达式不同,它们将转换为不同的代码结构。让我们看一个集合推导式:

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时,集合推导式会直接将值存储到正在构建的集合中。
这意味着,如果你在生成器表达式的主体中添加一个yield表达式,它将与语言为生成器主体构造的yield一样处理;结果是,每次迭代会得到两个(或多个)值。
然而,如果你向一个列表(集合、字典)推导式中添加一个yield,则推导式会从构建列表(集合、字典)的函数转化为执行yield语句的生成器,然后返回构造的列表(集合、字典)。集合推导式结果中的{None}是由每个yield表达式计算出的None构建的集合。
最后,为什么Python 3.3没有产生{None}?(请注意,之前的Python 3版本会产生。)这是因为PEP 380(即yield from支持)。在Python 3.3之前,在生成器中使用return会出现SyntaxError: 'return' with argument inside generator错误;因此,我们的yield推导式正在利用未定义的行为,但实际的RETURN_VALUE操作码的结果只是从生成器中生成另一个(最终)值。在Python 3.3中,return value得到了明确支持;RETURN_VALUE操作码将引发StopIteration,这将停止生成器而不产生最终值。

为什么 Python 3 列表 [None] * 5 不会产生 {None},而是会产生 [None, None, None, None, None] - Eric
实际上,在Python3中运行相同的代码并不会产生{None} - Eric
1
@Eric 是因为 PEP 380 (yield from) 的缘故。我会加上解释。 - ecatmur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接