列表推导将代码直接放在使用它们的函数中,如下所示:
>>> dis.dis((lambda: [a for b in c]))
1 0 BUILD_LIST 0
3 LOAD_GLOBAL 0 (c)
6 GET_ITER
>> 7 FOR_ITER 12 (to 22)
10 STORE_FAST 0 (b)
13 LOAD_GLOBAL 1 (a)
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 7
>> 22 RETURN_VALUE
而生成器表达式和dict / set comprehensions主要放在一个单独的嵌套函数中,如下所示:
>>> dis.dis((lambda: {a for b in c}))
1 0 LOAD_CONST 1 (<code object <setcomp> at 0x7ff41a3d59b0, file "<stdin>", line 1>)
3 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (c)
9 GET_ITER
10 CALL_FUNCTION 1
13 RETURN_VALUE
>>> dis.dis((lambda: {a for b in c}).func_code.co_consts[1])
1 0 BUILD_SET 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (b)
12 LOAD_GLOBAL 0 (a)
15 SET_ADD 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
在Python 3中,所有这些都放在嵌套函数中。
为什么代码放在一个单独的嵌套函数中?我依稀记得在很久以前读过一些关于想要修复理解和/或genexpr变量溢出到周围范围的人,这是解决这个问题的原因吗?
为什么列表推导的实现方式与Python 2中的其余部分不同?由于向后兼容性? (我想到虽然我在介绍了生成器表达式之后听到了关于溢出修复的讨论,但我可能刚刚阅读了很旧的讨论,或其他什么)
答案 0 :(得分:10)
是的,你是对的。在Python 3.x中,这是为了修复变量泄漏而引入的。引自History of Python blog的帖子,据说是由BDFL自己写的,
我们还在Python 3中进行了另一项更改,以改进列表推导和生成器表达式之间的等效性。 在Python 2中,列表理解&#34;泄漏&#34;循环控制变量进入周围范围:
x = 'before' a = [x for x in 1, 2, 3] print x # this prints '3', not 'before'
这是列表推导的原始实现的工件;它是Python&#34;肮脏的小秘密之一&#34;多年。 它最初只是为了让列表理解能够快速地进行故意的妥协,,虽然这对于初学者来说并不是一个常见的陷阱,但它偶尔会刺痛人们。对于生成器表达式,我们不能这样做。生成器表达式使用生成器实现,生成器的执行需要单独的执行帧。因此,生成器表达式(特别是如果它们迭代一个短序列)的效率低于列表推导。
然而,在Python 3中,我们决定修复&#34;肮脏的小秘密&#34;列表推导使用与生成器表达式相同的实现策略。因此,在Python 3中,上面的例子(修改后使用
print(x)
:-)将在&#39;之前打印,证明&#39; x&#39;在列表理解暂时阴影但不覆盖&#39; x&#39;在周围范围内。
所有问题都由突出显示的文字回答。