今天在写一些特别糟糕的代码时,我偶然发现了这种神秘的行为。下面的python3程序打印一个随机选择的属性。这是怎么发生的?在
不确定性的一个明显的疑点是vars(object)
字典的随机排序,但我不知道这是如何导致观察到的行为的。我的一个假设是,它是由__setattr__
的顺序被重写引起的,但事实证明lambda总是只被调用一次(通过打印调试检查)。在
class TypeUnion:
pass
class t:
pass
def super_serious(obj):
proxy = t()
for name, val in vars(object).items():
if not callable(val) or type(val) is type:
continue
try:
setattr(t, name, lambda _, *x, **y: val)
except AttributeError:
pass
return proxy
print(super_serious(TypeUnion()).x)
注意:上面的程序并没有试图做任何有用的事情;它比原来的程序大大减少了。在
Andrei Cioara's answer基本正确:
随机性来自Python3.3和后来默认的随机化散列顺序(参见Why is dictionary ordering non-deterministic?)。
访问
x
调用绑定到__getattribute__
的lambda函数。请参见Difference between __getattr__ vs __getattribute__和the Python3 datamodel reference notes for ^{} 。在
我们可以通过以下方式使整个事情变得不那么混乱:
这就是lambda的情况。注意,在循环中,我们不绑定/保存
val
的当前值。1这意味着我们得到了val
中最后一个值。对于原始代码,我们在创建对象t
时完成所有这些工作,而不是稍后调用t.__getattribute__
,但它仍然可以归结为:在vars(object)中的<;name,value>;对中,找到最后一个满足我们的条件:值必须是可调用的,而值的类型不是它本身type
。使用
^{pr2}$class t(object)
使t
成为一个新样式的类对象,甚至在Python2中也是如此,因此这段代码现在可以在Python2和Python3中“正常工作”。当然,在Py2k中,字典排序不是随机的,所以每次都会得到相同的结果:对比:
将环境变量
PYTHONHASHSEED
设置为0
,使得Python3中的顺序也具有确定性:1要了解这是怎么回事,请尝试以下操作:
注意,它说的是}。然后将lambda线替换为:
func() returns 2
,而不是{再运行一次。现在函数返回0。这是因为我们在这里保存了
i
的当前值。在如果我们对样本程序做同样的事情,它将返回满足条件的第一个
val
,而不是最后一个。在非确定性来自于
vars(object)
返回的__dict__
中的随机性打印有点可疑,因为你的字体联合没有“x”
返回某些内容的原因是因为for循环覆盖了
^{pr2}$__getattribute__
,并因此劫持了点。加上这一行就可以看出。在一旦
get
受损,set
也会死亡。就这样想吧在概念上与
但是
get
现在总是返回相同的引用,而不管name
的值是什么,然后被重写。因此,最终的答案将是这个迭代中的最后一个项目,这是随机的,因为for循环将经历初始__dict__
的随机顺序另外,请记住,如果您的目标是复制对象,那么{}是错误的。调用lambda只会返回原始函数,但不会调用原始函数,您需要
是的,torek是正确的,因为您的代码没有绑定
val
的当前值,所以您得到了分配给val
的最后一个值。下面是一个用闭包“正确”绑定值的版本:这将一致地输出}被劫持。在
<slot wrapper '__getattribute__' of 'object' objects>
,证明问题在于{相关问题 更多 >
编程相关推荐