为啥这个父类 setter 调用使用 type(self) 而不是 self?

Posted

技术标签:

【中文标题】为啥这个父类 setter 调用使用 type(self) 而不是 self?【英文标题】:Why does this parent class setter call use type(self) rather than self?为什么这个父类 setter 调用使用 type(self) 而不是 self? 【发布时间】:2021-06-05 08:05:51 【问题描述】:

Python @property inheritance the right way 解释了如何调用父 setter。

class Number:
    def __init__(self):
        self._value = None
    
    @property
    def value(self):
        assert self._value is not None
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value


class Integer(Number):
    @property
    def value(self):
        return super().value

    @value.setter
    def value(self, new_value):
        _value = int(new_value)
        super(Integer, type(self)).value.fset(self, _value) # <----- OK with using type(self)
        # super(Integer, self).value.fset(self, _value)     # <----- Assert error with self
        
i = Integer()
i.value = 1             # cause assertion error with "super(Integer, self)"
print(i.value) 

问题

使用 super(Integer, type(self)).value.fset(self, _value)i.value = 1 按预期调用设置器。

对于super(Integer, self).value.fset(self, _value)i.value = 1 调用的是getter 而不是setter,因此会导致断言错误。

AssertionError                            Traceback (most recent call last)
<ipython-input-8-2c57a07c128d> in <module>
     35 
     36 i = Integer()
---> 37 i.value = 1
     38 print(i.value)

<ipython-input-8-2c57a07c128d> in value(self, new_value)
     32         _value = int(new_value)
     33         #super(Integer, type(self)).value.fset(self, _value)
---> 34         super(Integer, self).value.fset(self, _value)
     35 
     36 i = Integer()

<ipython-input-8-2c57a07c128d> in value(self)
     10     @property
     11     def value(self):
---> 12         assert self._value is not None
     13         return self._value

问题

请帮助理解为什么super(Integer, self).value.fset(self, _value) 会使用getter 而不是setter,尽管调用fset。阅读文档和文章,在我看来传递对象 self 而不是类型/类 type(self) 是访问绑定到实例本身的方法的正确方法,但它不起作用。

super([type[, object-or-type]])

对象或类型决定方法解析顺序 搜索。搜索从类型之后的类开始。

例如,如果 mro 的 object-or-type 是 D -> B -> C -> A -> object 且 type 的值为 B,则 super() 搜索 C -> A -> 对象。

object-or-type 的 mro 属性列出了方法 getattr() 和 super() 使用的分辨率搜索顺序。这 属性是动态的,并且可以在继承层次结构时更改 已更新。

Supercharge Your Classes With Python super()

在 Python 3 中,super(Square, self) 调用等价于 无参数 super() 调用。第一个参数指的是子类 Square,而第二个参数是指一个 Square 对象,在 这个案子,是自己。您也可以使用其他类调用 super():

    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area * 6

    def volume(self):
        face_area = super(Square, self).area()
        return face_area * self.length 

第二个参数呢?请记住,这是一个对象 用作第一个参数的类的实例。例如, isinstance(Cube, Square) 必须返回 True。

通过包含一个实例化的对象,super() 返回一个绑定的方法:a 绑定到对象的方法,它赋予该方法 对象的上下文,例如任何实例属性。如果这个参数是 不包含,返回的方法只是一个函数,无关联 带有对象的上下文。

【问题讨论】:

【参考方案1】:

super(Integer, self).value.fset(self, _value)(或更简单的等价物super().value.fset(self, _value))的问题甚至在您到达fset 之前就已出现。描述符协议参与实例上的所有查找,只需执行super(Integer, self).value(或super().value)即可使其调用getter。这就是为什么您继承的吸气剂首先起作用的原因;它调用了property 描述符,并得到了它产生的值。

为了绕过描述符协议(更准确地说,从实例转移到类级别调用,propertys 在类级别场景中没有做任何特别的事情),您需要对类本身执行查找,而不是它的实例。 super(Integer, type(self)) 调用 super 的形式,它返回绑定在类级别而不是实例级别的 super 对象,允许您检索原始描述符本身,而不是调用描述符和获得它产生的价值。获得原始描述符后,您可以访问和调用附加到它的 fset 函数。

这与super 不涉及 时遇到的问题相同。如果你有一个Number 的实例,并且想直接访问fset 函数(而不是通过赋值隐式调用它),你必须这样做:

num = Number()
type(num).value.fset(num, 1)

因为做:

num.value.fset(num, 1)

当您检索 num.value(获取 getter 产生的 None)时失败,然后尝试在 None 上查找 fset

【讨论】:

"super(Integer, type(self)) 调用 super 的形式,它返回绑定在类级别而不是实例级别的超级对象,允许您检索原始描述符本身,而不是调用描述符并获取它产生的值。” - 好吧,它仍然调用描述符协议,但是描述符协议的类版本,而不是实例版本。这对于@classmethod 之类的东西很重要,但是对于属性,在类上查找只会返回属性对象。 @user2357112supportsMonica:是的,我不想在主要答案的正文中陷入困境。还有一种情况,instance 级描述符协议仍然被调用 if 类的元类定义了这样的描述符(因为所有类本身都是它们元类的实例;所有的都是海龟下降)。我为你的笔记添加了一个括号。 @ShadowRanger,非常感谢您的回答,很抱歉尚未接受,因为我需要时间来消化内容和描述符协议。

以上是关于为啥这个父类 setter 调用使用 type(self) 而不是 self?的主要内容,如果未能解决你的问题,请参考以下文章

子类为啥不能直接调用父类的属性

java中,当实例化子类时会递归调用父类中的构造方法。这个说法对么?为啥

为啥`type.__new__`调用`__init_subclass__`?

Java:父类对象为啥能转换成子类对象

为啥 Swift 不像 Java 或 C# 那样对属性使用 getter 和 setter?

KVO底层实现原理