为什么setattr(super(), ...)不等同于super().__setattr__(...)?

8
根据这个答案setattr(instance, name, value)instance.__setattr__(name, value)的简写。但是:
class C:
    def __init__(self):
        # OK.
        super().__setattr__("foo", 123)

        # AttributeError: 'super' object has no attribute 'foo'
        setattr(super(), "foo", 123)

c = C()

怎么回事?难道它们不应该做同样的事情吗?

1个回答

4
你提供的答案忽略了一些重要的细节。简而言之,setattr绕过了super的魔法,因此它试图在super()代理对象本身上设置属性,而不是在self上设置属性。

setattr(a, b, c)并不是a.__setattr__(b, c)的语法糖。 setattr和常规属性赋值都通过直接搜索对象类型的方法解析顺序(一个精心排序的类及其所有超类的列表)来查找__setattr__方法,绕过实例字典和__getattribute__/__getattr__钩子。

相反,a.__setattr__(b, c)只是执行__setattr__的常规属性查找,因此它会通过__getattribute____getattr__,并且除非__getattribute__指示不这样做,否则它会检查实例字典。


super 实现了一个自定义的 __getattribute__ 方法来进行其属性魔法。super().__setattr__ 使用这个钩子,在 C 的超类中找到 __setattr__ 并绑定 self,从而得到一个用于在 self 上设置属性的方法对象。

setattr(super(), ...) 绕过了 __getattribute__。它执行了先前提到的直接 MRO 搜索,通过搜索 super 的 MRO,在 超级实例本身上 找到了一个设置属性的方法对象。由于 super 实例没有 __dict__ 来存储任意属性数据,因此尝试设置 foo 属性会导致 TypeError


那么,super()的虚假性在使用setattr(super(), ...)时就不再成立了,因为super()实际上没有它所扮演的__mro__。这种限制在官方文档中有记录吗?super()文档中对于“使用语句或运算符进行隐式查找时未定义”有注释,但我认为这并不是一个隐式查找。 - Maxpm
@Maxpm:这与常规属性访问中涉及的查找一样隐式。尽管名称非常相似,但setattr不是显式的__setattr__查找。 - user2357112
关于super文档,实际上它们非常糟糕。它们甚至没有足够的解释来说明super的普通用法是如何工作的。尽管如此,它们确实解释了super是通过实现__getattribute__来工作的,而这种操作不会触发__getattribute__ - user2357112
@user2357112:你的回答没有解释setattr(...)instance.__setattr__(...)之间的区别。 - Ethan Furman
@EthanFurman:我添加了更多的解释。在不解释足够和过度解释太多分散注意力的细节之间找到正确的平衡是很困难的。我可以继续讲述常规属性查找、特殊方法查找和super.__getattribute__之间的差异,谈论tp_setattro以及MRO搜索通常如何被优化掉,谈论描述符协议,谈论描述符协议也通常被优化掉等等。 - user2357112
@user2357112:我在另一个问题上更新了我的答案,希望能得到您的反馈。 - Ethan Furman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接