为什么这个父类的设置器调用使用type(self)而不是self?

3

Python @property继承的正确方式解释了如何调用父类的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 按预期调用了setter。

使用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)调用fset时会进入getter而不是setter。阅读文档和文章,我发现将对象self传递给方法是访问绑定到实例本身的方法的正确方式,但它并没有起作用。

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

object-or-type 确定要搜索的方法解析顺序。搜索从类型后面的类开始。

例如,如果 object-or-type 的MRO为D -> B -> C -> A -> object,并且 type 的值为B,则 super() 将搜索C -> A -> object。

对象或类型的mro列出了getattr()和super()使用的方法解析搜索顺序。该属性是动态的,当继承层次结构更新时可能会更改。

使用Python super()强化您的类

In Python 3, the super(Square, self) call is equivalent to the parameterless super() call. The first parameter refers to the subclass Square, while the second parameter refers to a Square object which, in this case, is self. You can call super() with other classes as well:

    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 

What about the second parameter? Remember, this is an object that is an instance of the class used as the first parameter. For an example, isinstance(Cube, Square) must return True.

By including an instantiated object, super() returns a bound method: a method that is bound to the object, which gives the method the object’s context such as any instance attributes. If this parameter is not included, the method returned is just a function, unassociated with an object’s context.

1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
3
super(Integer, type(self)).value.fset(self, _value) 或者更简单的等价形式 Integer.value.fset(self, _value)。问题出现在你甚至还没有到达 fset 的时候。描述符协议在实例上所有的查找都会被启用,因此通过 super(Integer, self).value (或者 super().value) 来获取 getter 就会导致它被调用。这就是为什么你继承的 getter 起作用的原因;它调用了 property 描述符并得到了它产生的值。 为了避开描述符协议(更准确地说是从实例级别转移到类级别调用,在类级别场景中,property 没有特殊作用),你需要在类本身上执行查找,而不是在其实例上执行查找。super(Integer, type(self)) 调用返回一个在类级别上绑定的 super 对象的形式的 super,而不是在实例级别上绑定的 super,从而允许你检索原始描述符本身,而不是调用描述符并得到它生成的值。一旦你拥有了原始描述符,就可以访问和调用附加到它上面的 fset 函数。 当没有涉及到 super 时,你也会遇到同样的问题。如果你有一个 Number 的实例,并想要直接访问 fset 函数(而不是通过赋值隐式调用它),你必须执行: super(Integer, type(self)).value.fset(self, _value) 或者更简单的等价形式 Integer.value.fset(self, _value)
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 等内容很重要,但对于属性来说,在类上的查找只会返回属性对象。 - user2357112
@user2357112supportsMonica:是的,我不想在主答案中详细讨论这个问题。还有一个情况是,如果类的元类定义了这样的描述符(因为所有类本身都是它们的元类的实例;往下一直都是这样),仍会调用实例级别的描述符协议。我在括号中加了一句话,提到了你的注意事项。 - ShadowRanger
@ShadowRanger,非常感谢您的回答,很抱歉还没有接受,因为我需要时间来消化内容和描述协议。 - mon

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