为什么Python 2中的生成器表达式和dict/set理解使用嵌套函数而不是列表理解?

为什么Python 2中的生成器表达式和dict/set理解使用嵌套函数而不是列表理解?,python,bytecode,python-internals,Python,Bytecode,Python Internals,列表理解的代码直接放在使用它们的函数中,如下所示: >>> 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 (

列表理解的代码直接放在使用它们的函数中,如下所示:

>>> 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        
>>> 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        
而生成器表达式和dict/set理解大多放在单独的嵌套函数中,如下所示:

>>> 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        
>>> 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        
dis.dis((lambda:{a代表c}中的b)) 1 0加载常数1(<0x7ff41a3d59b0处的代码对象setcomp,文件“stdin”,第1行>) 3生成函数0 6加载_全局0(c) 9得到它 10调用函数1 13返回值 >>>dis.dis((lambda:{a代表c}.func_code.co_consts[1])) 1 0生成集0 3加载速度为0(.0) >>国际热核实验堆12(至21)6 9商店1(b) 12加载_全局0(a) 15套加2套 18绝对跳跃6 >>21返回值 在Python3中,所有这些都放在一个嵌套函数中

为什么要将代码放在单独的嵌套函数中?我模模糊糊地记得很久以前读到过一些关于人们想要修正理解和/或genexpr变量溢出到周围范围的文章,这是对那个的修正还是什么


为什么列表理解的实现与Python 2中的其他理解不同?因为向后兼容?(我想我在介绍生成器表达式后听到了很多关于溢出修复的讨论,但我可能只是在阅读一些非常古老的讨论或其他内容)

是的,你是对的。在Python3.x中,引入此选项是为了修复变量泄漏。引用了《华盛顿邮报》的一段话,据说是BDFL自己写的

我们还在Python3中做了另一个更改,以改进列表理解和生成器表达式之间的等价性在Python 2中,列表理解将循环控制变量“泄漏”到周围的范围中

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'
这是列表理解最初实现的产物;这是Python多年来“肮脏的小秘密”之一一开始,它是一种有意的妥协,让人们盲目地快速理解列表,虽然这对初学者来说不是一个常见的陷阱,但它肯定会偶尔刺痛人们。对于生成器表达式,我们无法执行此操作。生成器表达式使用生成器实现,生成器的执行需要单独的执行框架。因此,生成器表达式(特别是当它们在短序列上迭代时)的效率低于列表理解

然而,在Python3中,我们决定使用与生成器表达式相同的实现策略来修复列表理解的“肮脏小秘密”。因此,在Python3中,上面的示例(修改为使用
print(x)
:-)将打印“before”,证明列表中的“x”暂时隐藏,但不会覆盖周围范围中的“x”

突出显示的文本回答了您的所有问题。

以下是Nick Coghlan和Georg Brandl讨论以这种方式实施这些问题的基本原理的线索