为什么这段代码会打印一个随机选择的属性?
Why does this code print a randomly selected attribute?
今天在写一些特别糟糕的代码时,我偶然发现了这种神秘的行为。下面的 Python 3 程序打印了 object
的 随机选择的 属性。这是怎么发生的?
不确定性的一个明显怀疑是 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)
N.B。上面的程序并没有试图做任何有用的事情;它比原来的大大减少了。
不确定性来自 __dict__
return 中的随机性 vars(object)
打印有点可疑,因为您的 TypeUnion 没有 'x'
super_serious(TypeUnion()).x
某些东西被 returned 的原因是因为你的 for 循环覆盖了 __getattribute__
并因此劫持了点。添加此行将显示。
if name == '__getattribute__':
continue
一旦 get
被攻陷,set
也会死亡。可以这样想
setattr(t, name, lambda *x, **y: val)
在概念上与
相同
t.__dict__[name] = lambda *x, **y: val
但是 get
现在总是 return 相同的引用,无论 name
的值如何,然后被覆盖。因此,最终答案将是本次迭代中的最后一项,它是随机的,因为 for 循环经过初始 __dict__
的随机顺序
此外,请记住,如果您的目标是复制对象,那么 setattr
是错误的。调用 lambda 只会 return 原始函数,但不会调用原始函数,你需要一些类似于
的东西
setattr(t, name, lambda *x, **y: val(*x, **y) # Which doesn't work
基本正确:
随机性来自Python 3.3 及更高版本默认随机哈希顺序(参见Why is dictionary ordering non-deterministic?)。
访问x
调用已绑定到__getattribute__
的lambda函数。
参见 Difference between __getattr__ vs __getattribute__ and the Python3 datamodel reference notes for object.__getattribute__
。
我们可以通过以下方式使整个事情变得不那么混乱:
class t(object):
def __getattribute__(self, name):
use = None
for val in vars(object).values():
if callable(val) and type(val) is not type:
use = val
return use
def super_serious(obj):
proxy = t()
return proxy
lambda 就是这样。注意在循环中,我们没有绑定/保存val
的current值。1这意味着我们得到了last 值是 val
在函数中的值。使用原始代码,我们在创建对象 t
时完成所有这些工作,而不是稍后在 t.__getattribute__
被调用时完成——但它仍然归结为:Of pairs in vars(object),找到最后一个满足我们条件的:这个值必须是可调用的,而这个值的类型不是它自己 type
.
即使在 Python2 中,使用 class t(object)
使 t
成为新样式的 class 对象,因此此代码现在 "works" 在 [=85 中=]2 以及 Python3。当然,在 Py2k 中,字典排序不是随机的,所以每次我们得到的都是一样的东西:
$ python2 foo3.py
<slot wrapper '__init__' of 'object' objects>
$ python2 foo3.py
<slot wrapper '__init__' of 'object' objects>
对比:
$ python3 foo3.py
<slot wrapper '__eq__' of 'object' objects>
$ python3 foo3.py
<slot wrapper '__lt__' of 'object' objects>
将环境变量 PYTHONHASHSEED
设置为 0
也使 Python3 中的顺序具有确定性:
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
1要了解这是关于什么的,请尝试以下操作:
def f():
i = 0
ret = lambda: i
for i in range(3):
pass
return ret
func = f()
print('func() returns', func())
请注意,它说的是 func() returns 2
,而不是 func() return 0
。然后将 lambda 行替换为:
ret = lambda stashed=i: stashed
又 运行 了。现在函数returns 0。这是因为我们在这里保存了i
的current值。
如果我们对示例程序做同样的事情,它将 return 第一个 val
符合条件,而不是 return 最后一个一个。
是的,torek 是正确的,因为您的代码没有绑定 val
的当前值,所以您得到分配给 val
的最后一个值。这是 "correctly" 将值与闭包绑定的版本:
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 v: lambda _, *x, **y: v)(val))
except AttributeError:
pass
return proxy
print(super_serious(TypeUnion()).x)
这样会一直输出<slot wrapper '__getattribute__' of 'object' objects>
,证明问题是__getattribute__
被劫持了
今天在写一些特别糟糕的代码时,我偶然发现了这种神秘的行为。下面的 Python 3 程序打印了 object
的 随机选择的 属性。这是怎么发生的?
不确定性的一个明显怀疑是 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)
N.B。上面的程序并没有试图做任何有用的事情;它比原来的大大减少了。
不确定性来自 __dict__
return 中的随机性 vars(object)
打印有点可疑,因为您的 TypeUnion 没有 'x'
super_serious(TypeUnion()).x
某些东西被 returned 的原因是因为你的 for 循环覆盖了 __getattribute__
并因此劫持了点。添加此行将显示。
if name == '__getattribute__':
continue
一旦 get
被攻陷,set
也会死亡。可以这样想
setattr(t, name, lambda *x, **y: val)
在概念上与
相同t.__dict__[name] = lambda *x, **y: val
但是 get
现在总是 return 相同的引用,无论 name
的值如何,然后被覆盖。因此,最终答案将是本次迭代中的最后一项,它是随机的,因为 for 循环经过初始 __dict__
此外,请记住,如果您的目标是复制对象,那么 setattr
是错误的。调用 lambda 只会 return 原始函数,但不会调用原始函数,你需要一些类似于
setattr(t, name, lambda *x, **y: val(*x, **y) # Which doesn't work
随机性来自Python 3.3 及更高版本默认随机哈希顺序(参见Why is dictionary ordering non-deterministic?)。
访问
x
调用已绑定到__getattribute__
的lambda函数。
参见 Difference between __getattr__ vs __getattribute__ and the Python3 datamodel reference notes for object.__getattribute__
。
我们可以通过以下方式使整个事情变得不那么混乱:
class t(object):
def __getattribute__(self, name):
use = None
for val in vars(object).values():
if callable(val) and type(val) is not type:
use = val
return use
def super_serious(obj):
proxy = t()
return proxy
lambda 就是这样。注意在循环中,我们没有绑定/保存val
的current值。1这意味着我们得到了last 值是 val
在函数中的值。使用原始代码,我们在创建对象 t
时完成所有这些工作,而不是稍后在 t.__getattribute__
被调用时完成——但它仍然归结为:Of type
.
即使在 Python2 中,使用 class t(object)
使 t
成为新样式的 class 对象,因此此代码现在 "works" 在 [=85 中=]2 以及 Python3。当然,在 Py2k 中,字典排序不是随机的,所以每次我们得到的都是一样的东西:
$ python2 foo3.py
<slot wrapper '__init__' of 'object' objects>
$ python2 foo3.py
<slot wrapper '__init__' of 'object' objects>
对比:
$ python3 foo3.py
<slot wrapper '__eq__' of 'object' objects>
$ python3 foo3.py
<slot wrapper '__lt__' of 'object' objects>
将环境变量 PYTHONHASHSEED
设置为 0
也使 Python3 中的顺序具有确定性:
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
$ PYTHONHASHSEED=0 python3 foo3.py
<method '__subclasshook__' of 'object' objects>
1要了解这是关于什么的,请尝试以下操作:
def f():
i = 0
ret = lambda: i
for i in range(3):
pass
return ret
func = f()
print('func() returns', func())
请注意,它说的是 func() returns 2
,而不是 func() return 0
。然后将 lambda 行替换为:
ret = lambda stashed=i: stashed
又 运行 了。现在函数returns 0。这是因为我们在这里保存了i
的current值。
如果我们对示例程序做同样的事情,它将 return 第一个 val
符合条件,而不是 return 最后一个一个。
是的,torek 是正确的,因为您的代码没有绑定 val
的当前值,所以您得到分配给 val
的最后一个值。这是 "correctly" 将值与闭包绑定的版本:
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 v: lambda _, *x, **y: v)(val))
except AttributeError:
pass
return proxy
print(super_serious(TypeUnion()).x)
这样会一直输出<slot wrapper '__getattribute__' of 'object' objects>
,证明问题是__getattribute__
被劫持了