英文:
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("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)
And an ouput:
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
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("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)
答案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("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)
and an output:
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 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 beself.__class__
but I haven't seen this being mentioned anywhere. Interestingly enough, however, for class methods, like__new__()
, this second parameters defaults tocls
, so usingsuper()
within a class method without parameters is meaningful.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论