为什么列表推导写入循环variables,但生成器不?

如果我做了一些列表parsing,它写入一个局部variables:

i = 0 test = any([i == 2 for i in xrange(10)]) print i 

这打印“9”。 但是,如果我使用一个生成器,它不写入一个局部variables:

 i = 0 test = any(i == 2 for i in xrange(10)) print i 

这打印“0”。

这有什么不同吗? 这是一个devise决定,还是一个随机的产生器和列表parsing实现的副产品? 就个人而言,如果列表parsing不写入局部variables,对我来说似乎更好。

Python的创build者Guido van Rossum在写关于Python 3统一构build的生成器expression式时提到了这一点:(强调我的)

我们还在Python 3中做了另一个改进,以改善列表推导和生成器expression式之间的等价性。 在Python 2中,列表理解将循环控制variables“泄漏”到周围的范围中:

 x = 'before' a = [x for x in 1, 2, 3] print x # this prints '3', not 'before' 

这是列表解读的原始实施的一个人造物。 这是多年来Python的“肮脏的小秘密”之一。 它开始是一个故意的妥协,使列表解读盲目快速,虽然这不是一个初学者常见的陷阱,它肯定会偶尔刺痛人。 对于生成器expression式,我们不能这样做。 生成器expression式使用生成器实现,其执行需要单独的执行框架。 因此,生成器expression式(特别是如果它们遍历一个短序列)比列表parsing效率低。

然而,在Python 3中,我们决定使用与生成器expression式相同的实现策略来修复列表推导的“脏小秘密”。 因此,在Python 3中,上面的例子(使用print(x):-)进行修改之后将会打印'before',certificate列表理解中的'x'暂时阴影,但不会覆盖周围的'x'范围。

所以在Python 3中,你不会再看到这种情况了。

有趣的是,在Python 2中的字典理解也不这样做; 这主要是因为从Python 3中支持dict的理解,因此已经修复了它们。

还有一些其他的问题也涉及到这个话题,但是我相信当你search这个话题时你已经看到了这些话题,对吗? ;)

  • Python列表理解甚至在理解范围之后重新命名。 这是正确的吗?
  • 为什么操作完成后,列表理解variables是可访问的?

由于PEP 289 (生成器expression式)解释说:

循环variables(如果它是简单variables或简单variables的元组)不会暴露给周围的函数。 这有助于实施,并使典型用例更可靠。

这似乎是由于实施的原因。

就个人而言,如果列表parsing不写入局部variables,对我来说似乎更好。

PEP 289也阐明了这一点:

列表推导也将其循环variables“泄漏”到周围的范围中。 这在Python 3.0中也会改变,所以Python 3.0中的列表理解的语义定义将等同于list()。

换句话说,你描述的行为确实在Python 2中有所不同,但在Python 3中已经修复了。

就个人而言,如果列表parsing不写入局部variables,对我来说似乎更好。

你是对的。 这已经在Python 3.x中解决了。 2.x中的行为不变,所以它不会影响(ab)使用此洞的现有代码。

因为…因为。

不,真的,就是这样。 执行的怪癖。 可以说是一个错误,因为它已经在Python 3中修复了。

上面描述的肮脏的秘密的一个微妙的后果是list(...)和Python 2中没有相同的副作用:

 In [1]: a = 'Before' In [2]: list(a for a in range(5)) In [3]: a Out[3]: 'Before' 

所以在列表构造函数里面没有对生成器expression式的副作用,但副作用是直接列表理解的:

 In [4]: [a for a in range(5)] In [5]: a Out[5]: 4 

作为一个徘徊的副产品,如何实现列表理解,我发现你的问题很好的答案。

在Python 2中,查看为简单列表理解而生成的字节代码:

 >>> s = compile('[i for i in [1, 2, 3]]', '', 'exec') >>> dis(s) 1 0 BUILD_LIST 0 3 LOAD_CONST 0 (1) 6 LOAD_CONST 1 (2) 9 LOAD_CONST 2 (3) 12 BUILD_LIST 3 15 GET_ITER >> 16 FOR_ITER 12 (to 31) 19 STORE_NAME 0 (i) 22 LOAD_NAME 0 (i) 25 LIST_APPEND 2 28 JUMP_ABSOLUTE 16 >> 31 POP_TOP 32 LOAD_CONST 3 (None) 35 RETURN_VALUE 

它本质上是一个简单的for-loop ,这是它的语法糖。 因此,与for-loops相同的语义适用于:

 a = [] for i in [1, 2, 3] a.append(i) print(i) # 3 leaky 

在列表理解的情况下,(C)Python使用“隐藏列表名称”和一个特殊的指令LIST_APPEND来处理创build,但实际上没有什么比这更多的。

所以你的问题应该概括为什么Python写for循环的for循环variables; Eli Bendersky的博客文章很好地回答了这个问题 。

正如前面提到的和其他人所说,Python 3已经改变了列表理解的语义,以便更好地匹配生成器(通过为理解创build一个单独的代码对象),并且本质上是以下的语法糖:

 a = [i for i in [1, 2, 3]] # equivalent to def __f(it): _ = [] for i in it _.append(i) return _ a = __f([1, 2, 3]) 

这不会泄漏,因为它不会像Python 2等同的那样运行在最高层。 i泄漏,只在__f ,然后销毁作为该函数的局部variables。

如果你想的话,通过运行dis('a = [i for i in [1, 2, 3]]')来看看为Python 3生成的字节码。 你会看到一个“隐藏”的代码对象是如何加载的,然后进行一个函数调用。