使用super()和多重继承进行超类属性设置

huangapple go评论74阅读模式
英文:

Superclass property setting using super() and multiple inheritance

问题

在一个真实的程序中,我遇到了下面的问题:我有一个菱形继承,包括SuperClass、MidClassA、MidClassB和SubClass。SuperClass有一个属性(实际上是文件名),它被其派生类以不同的方式使用(有或没有扩展名)。在所有级别上,我想通过setter方法设置这个属性。以下是示例代码:

class SuperClass:
    def __init__(self):
        print("SuperClass __init__")
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value
        print("Superclass setter called!")


class MidClassA(SuperClass):
    def __init__(self):
        print("MidClassA __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassA, MidClassA).value.__set__(self, new_value)
        print("MidClassA setter called!", MidClassA.__mro__)


class MidClassB(SuperClass):
    def __init__(self):
        print("MidClassB __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassB, MidClassB).value.__set__(self, new_value)
        print("MidClassB setter called!", MidClassB.__mro__)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print("SubClass __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(SubClass, SubClass).value.__set__(self, new_value)
        print("Subclass setter called!", SubClass.__mro__)

obj = SubClass()
obj.value = 42
print(obj.value)

输出如下:

SubClass __init__
MidClassB __init__
MidClassA __init__
SuperClass __init__
Superclass setter called!
MidClassB setter called! (<class '__main__.MidClassB'>, <class '__main__.SuperClass'>, <class 'object'>)
Subclass setter called! (<class '__main__.SubClass'>, <class '__main__.MidClassB'>, <class '__main__.MidClassA'>, <class '__main__.SuperClass'>, <class 'object'>)
42

问题:

  • 如输出所示,__init__s的MRO(方法解析顺序)工作正常(SubClass-MidClassB-MidClassA-SuperClass-object),但在属性设置期间,MidClassA被跳过。
  • 如何摆脱super(MidClassB, MidClassB)
  • 是否可能摆脱__set__方法?

理想的解决方案将类似于super().value = new_value,但所有这些都不起作用。

英文:

In a real world program I have ran into the next problem: I have a diamond inheritance having SuperClass, MidClassA, MidClassB and SubClass. SuperClass has a property (a filename, actually) that is used by its successors, but different ways (with or without extension). At all levels I want to set this property by a setter. Here is an example code:

class SuperClass:
    def __init__(self):
        print(&quot;SuperClass __init__&quot;)
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value
        print(&quot;Superclass setter called!&quot;)


class MidClassA(SuperClass):
    def __init__(self):
        print(&quot;MidClassA __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassA, MidClassA).value.__set__(self, new_value)
        print(&quot;MidClassA setter called!&quot;, MidClassA.__mro__)


class MidClassB(SuperClass):
    def __init__(self):
        print(&quot;MidClassB __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassB, MidClassB).value.__set__(self, new_value)
        print(&quot;MidClassB setter called!&quot;, MidClassB.__mro__)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print(&quot;SubClass __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(SubClass, SubClass).value.__set__(self, new_value)
        print(&quot;Subclass setter called!&quot;, SubClass.__mro__)


obj = SubClass()
obj.value = 42
print(obj.value)

And an ouput:

SubClass __init__
MidClassB __init__
MidClassA __init__
SuperClass __init__
Superclass setter called!
MidClassB setter called! (&lt;class &#39;__main__.MidClassB&#39;&gt;, &lt;class &#39;__main__.SuperClass&#39;&gt;, &lt;class &#39;object&#39;&gt;)
Subclass setter called! (&lt;class &#39;__main__.SubClass&#39;&gt;, &lt;class &#39;__main__.MidClassB&#39;&gt;, &lt;class &#39;__main__.MidClassA&#39;&gt;, &lt;class &#39;__main__.SuperClass&#39;&gt;, &lt;class &#39;object&#39;&gt;)
42

Questions:

  • As output displays, the MRO of __init__s works well (SubClass-MidClassB-MidClassA-SuperClass-object) but during the propery setting MidClassA is skipped.
  • How to get rid of the super(MidClassB, MidClassB)?
  • Is it possible to get rid of the __set__ method?

An ideal solution would be something like super().value = new_value, but all these don't work.

答案1

得分: 1

考虑不修改这个属性,而是覆盖被 setter 使用的常规实例方法。

class SuperClass:
    def __init__(self):
        print("SuperClass __init__")
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value_setter(new_value)

    def _value_setter(self, new_value):
        print("Superclass setter called!")
        self._value = new_value


class MidClassA(SuperClass):
    def __init__(self):
        print("MidClassA __init__")
        super().__init__()

    def _value_setter(self, new_value):
        print("MidClassA setter called!", MidClassA.__mro__)
        super()._value_setter(new_value)


class MidClassB(SuperClass):
    def __init__(self):
        print("MidClassB __init__")
        super().__init__()

    def _value_setter(self, new_value):
        print("MidClassB setter called!", MidClassB.__mro__)
        super()._value_setter(new_value)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print("SubClass __init__")
        super().__init__()

    def _value_setter(self, new_value):
        print("Subclass setter called!", SubClass.__mro__)
        super()._value_setter(new_value)

obj = SubClass()
obj.value = 42
print(obj.value)
英文:

Consider leaving the property alone, and instead overriding a regular instance method used by the setter.

class SuperClass:
    def __init__(self):
        print(&quot;SuperClass __init__&quot;)
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value_setter(new_value)

    def _value_setter(self, new_value):
        print(&quot;Superclass setter called!&quot;)
        self._value = new_value


class MidClassA(SuperClass):
    def __init__(self):
        print(&quot;MidClassA __init__&quot;)
        super().__init__()

    def _value_setter(self, new_value):
        print(&quot;MidClassA setter called!&quot;, MidClassA.__mro__)
        super()._value_setter(new_value)


class MidClassB(SuperClass):
    def __init__(self):
        print(&quot;MidClassB __init__&quot;)
        super().__init__()

    def _value_setter(self, new_value):
        print(&quot;MidClassB setter called!&quot;, MidClassB.__mro__)
        super()._value_setter(new_value)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print(&quot;SubClass __init__&quot;)
        super().__init__()

    def _value_setter(self, new_value):
        print(&quot;Subclass setter called!&quot;, SubClass.__mro__)
        super()._value_setter(new_value)


obj = SubClass()
obj.value = 42
print(obj.value)

答案2

得分: 0

在发布这篇文章后,我意识到上面提到的三个问题中有两个有一个很好的解决方案:

super(MidClassB, MidClassB) 是一个错误的表达,因为它在MRO中“定位”到了与对象的实际类相关的同一个类。因此,正确的指定方式是super(MidClassB, self.__class__)。这将返回将调用__set__方法的正确类。

唯一剩下的问题是是否可能有一个实例setter,而不是这个classmethod __set__(cls, instance, value)

所以,下面是一段可工作的代码:

class SuperClass:
    def __init__(self):
        print("SuperClass __init__")
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value
        print("Superclass setter called!")


class MidClassA(SuperClass):
    def __init__(self):
        print("MidClassA __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassA, self.__class__).value.__set__(self, new_value)
        print("MidClassA setter called!", MidClassA.__mro__)


class MidClassB(SuperClass):
    def __init__(self):
        print("MidClassB __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassB, self.__class__).value.__set__(self, new_value)
        print("MidClassB setter called!", MidClassB.__mro__)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print("SubClass __init__")
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(SubClass, self.__class__).value.__set__(self, new_value)
        print("Subclass setter called!", SubClass.__mro__)


obj = SubClass()
obj.value = 42
print(obj.value)

输出如下:

SubClass __init__
MidClassB __init__
MidClassA __init__
SuperClass __init__
Superclass setter called!
MidClassA setter called! (<class '__main__.MidClassA'>, <class '__main__.SuperClass'>, <class 'object'>)
MidClassB setter called! (<class '__main__.MidClassB'>, <class '__main__.SuperClass'>, <class 'object'>)
Subclass setter called! (<class '__main__.SubClass'>, <class '__main__.MidClassB'>, <class '__main__.MidClassA'>, <class '__main__.SuperClass'>, <class 'object'>)
42

PS 据我了解,super() 是Python中一个重要而实用但文档不足的函数。它接受两个参数。

  • 第一个参数必须是调用对象的父类之一,标志着方法的搜索从哪里开始:首先要搜索的类是在参数中提到的类的直接父类(实际上是方法解析顺序中的下一个类)。默认情况下,这是调用super()的类。
  • 第二个参数是一个类或一个实例,它定义了将调用上述方法的对象,即此对象将作为第一个参数传递给该方法。对于绝大多数情况下,这是self(这是此参数的默认值),但对于类参数来说,这应该是self.__class__,但我还没有在任何地方看到有提到这一点。有趣的是,对于类方法(如__new__()),这个第二个参数默认为cls,因此在没有参数的情况下在类方法中使用super()是有意义的。
英文:

Just after posting this I realized that two of the three above mentioned questions has a good solution:

super(MidClassB, MidClassB) is a mistaken expression as it "positions" to the same class in MRO regarding the actual class of the object. So the proper designation is super(MidClassB, self.__class__). This will return the proper class those set method will be called.

The only question left is whether it is possible to have a instance setter instead of this classmethod __set__(cls, instance, value).

So a working piece of code is here:

class SuperClass:
    def __init__(self):
        print(&quot;SuperClass __init__&quot;)
        super().__init__()

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value
        print(&quot;Superclass setter called!&quot;)


class MidClassA(SuperClass):
    def __init__(self):
        print(&quot;MidClassA __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassA, self.__class__).value.__set__(self, new_value)
        print(&quot;MidClassA setter called!&quot;, MidClassA.__mro__)


class MidClassB(SuperClass):
    def __init__(self):
        print(&quot;MidClassB __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(MidClassB, self.__class__).value.__set__(self, new_value)
        print(&quot;MidClassB setter called!&quot;, MidClassB.__mro__)


class SubClass(MidClassB, MidClassA):
    def __init__(self):
        print(&quot;SubClass __init__&quot;)
        super().__init__()

    @SuperClass.value.setter
    def value(self, new_value):
        super(SubClass, self.__class__).value.__set__(self, new_value)
        print(&quot;Subclass setter called!&quot;, SubClass.__mro__)


obj = SubClass()
obj.value = 42
print(obj.value)

and an output:

SubClass __init__
MidClassB __init__
MidClassA __init__
SuperClass __init__
Superclass setter called!
MidClassA setter called! (&lt;class &#39;__main__.MidClassA&#39;&gt;, &lt;class &#39;__main__.SuperClass&#39;&gt;, &lt;class &#39;object&#39;&gt;)
MidClassB setter called! (&lt;class &#39;__main__.MidClassB&#39;&gt;, &lt;class &#39;__main__.SuperClass&#39;&gt;, &lt;class &#39;object&#39;&gt;)
Subclass setter called! (&lt;class &#39;__main__.SubClass&#39;&gt;, &lt;class &#39;__main__.MidClassB&#39;&gt;, &lt;class &#39;__main__.MidClassA&#39;&gt;, &lt;class &#39;__main__.SuperClass&#39;&gt;, &lt;class &#39;object&#39;&gt;)
42

PS as far as I understand super() is an important and practical, yet heavily underdocumented function in Python. It takes two parameters.

  • The first one must be a class from among the caller object's parents and this marks where the search begins for the method: the first class to be searched is the direct parent of the class mentioned in the parameter (actually the next one in the Method Resolution Order). By default this is the class where super() is called.
  • The second parameter is a class or an instance and this defines the object which the aforementioned method will be called upon, ie. this object will be passed to the method as the first parameter. For instance parameters in 99% of the cases this is the self (and this is the default value of this parameter), while for class parameters this should be self.__class__ but I haven't seen this being mentioned anywhere. Interestingly enough, however, for class methods, like __new__(), this second parameters defaults to cls, so using super() within a class method without parameters is meaningful.

huangapple
  • 本文由 发表于 2023年5月28日 22:22:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76351958.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定