为什么这段代码打印一个随机选择的属性?

2024-06-02 05:33:06 发布

您现在位置:Python中文网/ 问答频道 /正文

今天在写一些特别糟糕的代码时,我偶然发现了这种神秘的行为。下面的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)

注意:上面的程序并没有试图做任何有用的事情;它比原来的程序大大减少了。在


Tags: lambda代码name程序objecttypevalpass
3条回答

Andrei Cioara's answer基本正确:

  1. 随机性来自Python3.3和后来默认的随机化散列顺序(参见Why is dictionary ordering non-deterministic?)。

  2. 访问x调用绑定到__getattribute__的lambda函数。

请参见Difference between __getattr__ vs __getattribute__the Python3 datamodel reference notes for ^{}。在

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

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当前值。1这意味着我们得到了val中最后一个值。对于原始代码,我们在创建对象t时完成所有这些工作,而不是稍后调用t.__getattribute__,但它仍然可以归结为:在vars(object)中的<;name,value>;对中,找到最后一个满足我们的条件:值必须是可调用的,而值的类型不是它本身type

使用class t(object)使t成为一个新样式的类对象,甚至在Python2中也是如此,因此这段代码现在可以在Python2和Python3中“正常工作”。当然,在Py2k中,字典排序不是随机的,所以每次都会得到相同的结果:

^{pr2}$

对比:

$ 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,而不是{}。然后将lambda线替换为:

    ret = lambda stashed=i: stashed

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

如果我们对样本程序做同样的事情,它将返回满足条件的第一个val,而不是最后一个。在

非确定性来自于vars(object)返回的__dict__中的随机性

打印有点可疑,因为你的字体联合没有“x”

super_serious(TypeUnion()).x 

返回某些内容的原因是因为for循环覆盖了__getattribute__,并因此劫持了点。加上这一行就可以看出。在

^{pr2}$

一旦get受损,set也会死亡。就这样想吧

setattr(t, name, lambda *x, **y: val)

在概念上与

t.__dict__[name] = lambda *x, **y: val

但是get现在总是返回相同的引用,而不管name的值是什么,然后被重写。因此,最终的答案将是这个迭代中的最后一个项目,这是随机的,因为for循环将经历初始__dict__的随机顺序

另外,请记住,如果您的目标是复制对象,那么{}是错误的。调用lambda只会返回原始函数,但不会调用原始函数,您需要

setattr(t, name, lambda *x, **y: val(*x, **y)  # Which doesn't work

是的,torek是正确的,因为您的代码没有绑定val的当前值,所以您得到了分配给val的最后一个值。下面是一个用闭包“正确”绑定值的版本:

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>,证明问题在于{}被劫持。在

相关问题 更多 >