为什么此代码会打印随机选择的属性?

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)

注意:上述程序并未试图做任何有用的事情; 它比原来大大减少了.

tor*_*rek 3

安德烈·西奥拉 (Andrei Cioara) 的回答基本上是正确的:

\n\n
    \n
  1. 随机性来自 Python 3.3 和更高版本默认随机化哈希顺序(请参阅为什么字典排序是非确定性的?)。

  2. \n
  3. 访问x调用已绑定到 的 lambda 函数__getattribute__

  4. \n
\n\n

请参阅__getattr__ 与 __getattribute__ 之间的差异以及的 Python3 数据模型参考说明object.__getattribute__

\n\n

我们可以通过以下方式使整个事情变得不那么混乱:

\n\n
class 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

这与 lambda 发生的情况类似。请注意,在循环中,我们不绑定/保存的当前val1 这意味着我们得到了函数中的最后一个值。val使用原始代码,我们在创建 object 时完成所有这些工作t,而不是稍后t.__getattribute__被调用 \xe2\x80\x94 但它仍然归结为:在 vars(object) 中的 <name, value> 对中,找到最后一个符合我们的标准:该值必须是可调用的,而该值的类型不是它本身type

\n\n

即使在 Python2 中,使用也会class t(object)生成t新样式的类对象,因此该代码现在可以在 Python2 和 Python3 中“运行”。当然,在 Py2k 中,字典排序不是随机的,因此我们每次都会得到相同的结果:

\n\n
$ python2 foo3.py\n<slot wrapper \'__init__\' of \'object\' objects>\n$ python2 foo3.py\n<slot wrapper \'__init__\' of \'object\' objects>\n
Run 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>\n
Run Code Online (Sandbox Code Playgroud)\n\n

设置环境变量PYTHONHASHSEED0使 Python3 中的顺序也具有确定性:

\n\n
$ 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>\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

1要了解这是什么意思,请尝试以下操作:

\n\n
def 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())\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,它说func() returns 2,不是func() return 0。然后将 lambda 行替换为:

\n\n
    ret = lambda stashed=i: stashed\n
Run Code Online (Sandbox Code Playgroud)\n\n

并再次运行它。现在函数返回0。这是因为我们保存了当前i

\n\n

如果我们对示例程序执行同样的操作,它将返回第一个 val符合条件的程序,而不是最后一个一个。

\n