为什么Python 2中的生成器表达式和字典/集合理解使用嵌套函数而不像列表推导?

Ale*_*amo 8 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 (to 22)
             10 STORE_FAST               0 (b)
             13 LOAD_GLOBAL              1 (a)
             16 LIST_APPEND              2
             19 JUMP_ABSOLUTE            7
        >>   22 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

而生成器表达式和字典/集合理解主要放在一个单独的嵌套函数中,如下所示:

>>> 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        
Run Code Online (Sandbox Code Playgroud)

在Python 3中,所有这些都放在嵌套函数中.

为什么代码放在一个单独的嵌套函数中?我依稀记得在很久以前读过一些关于想要修复理解和/或genexpr变量溢出到周围范围的人的事情,这是针对那个还是什么的解决方案?

为什么列表推导的实现与Python 2中的其余部分不同?由于向后兼容性?(我认为在引入生成器表达式之后,我听说过很多关于溢出修复的讨论,但是我可能刚刚读过很旧的讨论,或者其他什么)

the*_*eye 10

是的,你是对的.在Python 3.x中,这是为了修复变量泄漏而引入的.引自Python博客的帖子,据说是由BDFL本人写的,

我们还在Python 3中进行了另一项更改,以改进列表推导和生成器表达式之间的等效性.在Python 2中,列表推导将循环控制变量"泄漏"到周围的范围中:

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'
Run Code Online (Sandbox Code Playgroud)

这是列表推导的原始实现的工件; 多年来它一直是Python"肮脏的小秘密"之一.起初是一种故意的妥协,使列表理解能够快速地进行,虽然它对于初学者来说不是常见的陷阱,但它偶尔会刺激人们.对于生成器表达式,我们不能这样做.生成器表达式使用生成器实现,生成器的执行需要单独的执行帧.因此,生成器表达式(特别是如果它们在短序列上迭代)的效率低于列表推导.

但是,在Python 3中,我们决定使用与生成器表达式相同的实现策略来修复列表推导的"脏小秘密".因此,在Python 3中,上面的例子(在修改后使用print(x):-)将打印'before',证明列表推导中的'x'暂时阴影但不覆盖周围范围中的'x'.

所有问题都由突出显示的文字回答.

  • 这是[链接](https://mail.python.org/pipermail/python-3000/2007-March/006017.html),其中Nick Coghlan和Georg Brandl讨论了以这种方式实现它们的理由 (2认同)