为什么列表推导写入循环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生成的字节码。 你会看到一个“隐藏”的代码对象是如何加载的,然后进行一个函数调用。