Python究竟如何评估类属性?我偶然发现了一个有趣的怪癖(在Python 2.5.2中),我想解释一下。
我有一个类,其中一些属性是根据其他先前定义的属性定义的。当我尝试使用生成器对象时,Python会抛出一个错误,但如果我使用普通的普通列表解析,那就没问题了。
这是精简的例子。请注意,唯一的区别是Brie
使用生成器表达式,而Cheddar
使用列表推导。
# Using a generator expression as the argument to list() fails
>>> class Brie :
... base = 2
... powers = list(base**i for i in xrange(5))
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in Brie
File "<stdin>", line 3, in <genexpr>
NameError: global name 'base' is not defined
# Using a list comprehension works
>>> class Cheddar :
... base = 2
... powers = [base**i for i in xrange(5)]
...
>>> Cheddar.powers
[1, 2, 4, 8, 16]
# Using a list comprehension as the argument to list() works
>>> class Edam :
... base = 2
... powers = list([base**i for i in xrange(5)])
...
>>> Edam.powers
[1, 2, 4, 8, 16]
(我的实际案例更复杂,我正在创建一个词典,但这是我能找到的最小例子。)
我唯一的猜测是列表推导是在该行计算的,但生成器表达式是在类结束后计算的,此时范围已经改变。但我不确定为什么生成器表达式不会作为闭包并将引用存储在该行的作用域中。
这是否有原因,如果是这样,我应该如何考虑类属性的评估机制?
答案 0 :(得分:14)
是的,这有点狡猾,这个。一个类并没有真正引入一个新的范围,它只是看起来有点像它;像这样的结构揭示了差异。
这个想法是,当你使用生成器表达式时,它等同于使用lambda:
class Brie(object):
base= 2
powers= map(lambda i: base**i, xrange(5))
或明确作为函数语句:
class Brie(object):
base= 2
def __generatePowers():
for i in xrange(5):
yield base**i
powers= list(__generatePowers())
在这种情况下,显然base
不在__generatePowers
的范围内;两者都有异常结果(除非你不幸的是也有base
全局,在这种情况下你会得到错误。)
由于关于如何评估它们的一些内部细节,列表推导不会发生这种情况,但是这种行为在Python 3中消失了,这两种情况都会失败。 Some discussion here.
使用lambda的解决方法可以使用我们在nested_scopes之前的旧时代所依赖的相同技术:
class Brie(object):
base= 2
powers= map(lambda i, base= base: base**i, xrange(5))
答案 1 :(得分:1)
来自PEP 289:
在探索了许多可能性后,a 达成共识是有约束力的问题 很难理解和用户 应该强烈鼓励使用 函数内的生成器表达式 消耗他们的论据 立即。更复杂 应用,全发电机 定义总是优越的 关于范围明显的条款, 终生和约束[6]。
[6](1,2)关于Source Forge的补丁讨论和替代补丁http://www.python.org/sf/872326
这就是我可以确定生成器表达式的范围。