fee*_*sum 5 python python-3.x name-lookup
今天在编写一些特别糟糕的代码时,我偶然发现了这种神秘的行为.下面的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)
Run Code Online (Sandbox Code Playgroud)
注意:上述程序并未试图做任何有用的事情; 它比原来大大减少了.
安德烈·西奥拉 (Andrei Cioara) 的回答基本上是正确的:
\n\n随机性来自 Python 3.3 和更高版本默认随机化哈希顺序(请参阅为什么字典排序是非确定性的?)。
访问x调用已绑定到 的 lambda 函数__getattribute__。
请参阅__getattr__ 与 __getattribute__ 之间的差异以及的 Python3 数据模型参考说明object.__getattribute__。
我们可以通过以下方式使整个事情变得不那么混乱:
\n\nclass t(object):\n def __getattribute__(self, name):\n use = None\n for val in vars(object).values():\n if callable(val) and type(val) is not type:\n use = val\n return use\n\ndef super_serious(obj):\n proxy = t()\n return proxy\nRun Code Online (Sandbox Code Playgroud)\n\n这与 lambda 发生的情况类似。请注意,在循环中,我们不绑定/保存的当前值val。1 这意味着我们得到了函数中的最后一个值。val使用原始代码,我们在创建 object 时完成所有这些工作t,而不是稍后t.__getattribute__被调用 \xe2\x80\x94 但它仍然归结为:在 vars(object) 中的 <name, value> 对中,找到最后一个符合我们的标准:该值必须是可调用的,而该值的类型不是它本身type。
即使在 Python2 中,使用也会class t(object)生成t新样式的类对象,因此该代码现在可以在 Python2 和 Python3 中“运行”。当然,在 Py2k 中,字典排序不是随机的,因此我们每次都会得到相同的结果:
$ python2 foo3.py\n<slot wrapper \'__init__\' of \'object\' objects>\n$ python2 foo3.py\n<slot wrapper \'__init__\' of \'object\' objects>\nRun Code Online (Sandbox Code Playgroud)\n\n对比:
\n\n$ python3 foo3.py\n<slot wrapper \'__eq__\' of \'object\' objects>\n$ python3 foo3.py\n<slot wrapper \'__lt__\' of \'object\' objects>\nRun Code Online (Sandbox Code Playgroud)\n\n设置环境变量PYTHONHASHSEED以0使 Python3 中的顺序也具有确定性:
$ PYTHONHASHSEED=0 python3 foo3.py\n<method \'__subclasshook__\' of \'object\' objects>\n$ PYTHONHASHSEED=0 python3 foo3.py\n<method \'__subclasshook__\' of \'object\' objects>\n$ PYTHONHASHSEED=0 python3 foo3.py\n<method \'__subclasshook__\' of \'object\' objects>\nRun Code Online (Sandbox Code Playgroud)\n\n1要了解这是什么意思,请尝试以下操作:
\n\ndef f():\n i = 0\n ret = lambda: i\n for i in range(3):\n pass\n return ret\nfunc = f()\nprint(\'func() returns\', func())\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,它说func() returns 2,不是func() return 0。然后将 lambda 行替换为:
ret = lambda stashed=i: stashed\nRun Code Online (Sandbox Code Playgroud)\n\n并再次运行它。现在函数返回0。这是因为我们保存了当前值i。
如果我们对示例程序执行同样的操作,它将返回第一个 val符合条件的程序,而不是最后一个一个。
| 归档时间: |
|
| 查看次数: |
152 次 |
| 最近记录: |