Python super() 无法调用父类的继承方法
Python super() can't call parent's inherited method
我在 Python 中有一个问题对我来说似乎非常复杂,它结合了继承、递归和 super() 函数。
首先,我用的是Python3,我的结构是深度继承的
在第一个父级 class 中,我声明了一个方法,我希望从层次结构中的每个子级 class 调用该方法,但每个子级具有不同的输入。
对我来说,使用该结构似乎非常 pythonic,它确实使我免于重复大量代码。
我的代码的简化示例如下所示:
class ClassA(object):
def __init__(self):
self.attr = 'a'
@classmethod
def _classname(cls):
return cls.__name__
def method1(self):
if self._classname() != 'ClassA': #don't call it for the very first parent class
super().method1()
print(self.attr)
class ClassB(ClassA):
def __init__(self):
self.attr = 'b'
class ClassC(ClassB):
def __init__(self):
self.attr = 'c'
inst1 = ClassC()
inst1.method1()
我希望该代码能够打印
'a'
'b'
'c'
相反,它会引发属性错误:
super().method1()
AttributeError: 'super' object has no attribute 'method1'
我知道这是一个复杂的问题,但我试图将它分解。我试图删除递归部分,但我没有得到任何改善。
根据我所做的各种尝试,我相信我已经非常接近问题的原因了,在我看来它就像一个语法问题或者那么简单的问题。
谢谢!!
那是因为您正试图在 Object_class
上访问 Method1()
def method1(self):
# this will print the class name you're calling from, if you are confused.
print(self._classname())
print(self.attr)
您编写了一个 if-else,当您使用 Class C 实例化并访问 method1() 时,它会满足。
要打印 a、b、c,您需要重写每个 class.. 中的方法并从中调用 super。
但是,这仍然会有问题。因为 self 属性带有 Class C 的实例,而不是 Class B 或 A。所以,它们无法访问您在 init[=21 中初始化的属性=] 函数。
最终代码如下所示
class ClassA(object):
def __init__(self):
self.attr = 'a'
@classmethod
def _classname(cls):
return cls.__name__
def method1(self):
print(self._classname())
#don't call it for the very first parent class
#super().method1()
print(self.attr)
class ClassB(ClassA):
def __init__(self):
self.attr = 'b'
def method1(self):
print(self._classname())
print(self.attr)
super().method1()
class ClassC(ClassB):
def __init__(self):
self.attr = 'c'
def method1(self):
print(self._classname())
print(self.attr)
super().method1()
inst1 = ClassC()
inst1.method1()
恐怕您在 Python 实例和 classes 之间建立了错误的心理模型。 类 只为 'inherit' 的实例提供一系列属性,而不是单独的名称 space 实例属性。当您在实例上查找属性时,该属性不存在如果实例本身不存在,则搜索支持实例的 classes,'nearest' class 具有该属性胜过其他属性。 super()
只允许您访问具有相同名称但定义在同一分层搜索 space 的下一个 class 上的属性 space。
为了super()
正常工作,Python records what class the method1
function was defined on。这里是 ClassA
,super()
将 仅 在 ClassA
的父 class 上查找属性。在您的示例中,ClassC
和 ClassB
已被搜索,并且它们没有 method1
属性,因此正在使用 ClassA.method1
,但没有进一步的 method1
属性在其余搜索到的层中(只剩下object
,没有object.method1
)。
当 subclasses 没有重写方法时,你不需要使用 super()
,你也不能用 super()
做你想做的事。请注意 ClassB
和 ClassC
subclasses 根本没有得到该方法的副本,没有 ClassC.method1
直接属性需要考虑 ClassB.method1
存在等。同样,在实例上查找属性时发生的情况是检查实例继承层次结构中的 all class 对象的该属性,按特定顺序排列。
看看你的子classes:
>>> inst1
<__main__.ClassC object at 0x109a9dfd0>
>>> type(inst1)
<class '__main__.ClassC'>
>>> type(inst1).__mro__
(<class '__main__.ClassC'>, <class '__main__.ClassB'>, <class '__main__.ClassA'>, <class 'object'>)
__mro__
属性为您提供 ClassC
class 对象的 方法解析顺序 ;搜索属性的顺序就是这个顺序,super()
使用这个顺序进一步搜索属性。要查找 inst1.method
,Python 将逐步遍历 type(inst1).__mro__
中的每个对象,并将 return 第一个命中,因此 ClassA.method1
.
在您的示例中,您在 ClassA.method1()
定义中使用了 super()
。 Python 已将一些信息附加到该函数对象以帮助进一步搜索属性:
>>> ClassA.method1.__closure__
(<cell at 0x109a3fee8: type object at 0x7fd7f5cd5058>,)
>>> ClassA.method1.__closure__[0].cell_contents
<class '__main__.ClassA'>
>>> ClassA.method1.__closure__[0].cell_contents is ClassA
True
当您调用 super()
时,我在上面显示的闭包用于沿着 type(self).__mro__
序列开始搜索,从闭包中命名的下一个对象开始。这里有 subclass 并不重要,因此即使对于您的 inst1
对象,也将跳过所有内容,仅检查 object
:
>>> type(inst1).__mro__.index(ClassA) # where is ClassA in the sequence?
2
>>> type(inst1).__mro__[2 + 1:] # `super().method1` will only consider these objects, *past* ClassA
(<class 'object'>,)
这里不再涉及 ClassB
或 ClassC
。 MRO 依赖于当前实例的 class 层次结构,当您开始使用 多重继承 时,您可以进行彻底的更改。在层次结构中添加额外的 classes 可以改变 MRO,足以在 ClassA
和 object
之间插入一些东西:
>>> class Mixin(object):
... def method1(self):
... print("I am Mixin.method1!")
...
>>> class ClassD(ClassA, Mixin): pass
...
>>> ClassD.__mro__
(<class '__main__.ClassD'>, <class '__main__.ClassA'>, <class '__main__.Mixin'>, <class 'object'>)
>>> ClassD.__mro__[ClassD.__mro__.index(ClassA) + 1:]
(<class '__main__.Mixin'>, <class 'object'>)
ClassD
继承自 ClassA
和 继承自 Mixin
。 Mixin
也继承自 object
。 Python 跟随一些 complicated rules 将层次结构中的所有 class 放入逻辑线性顺序中,并且 Mixin
最终位于 ClassA
和 object
之间因为它继承自后者,而不是前者。
因为 Mixin
在 ClassA
之后被注入 MRO ,调用 Class().method1()
改变了 super().method1()
的行为方式,突然调用该方法会做一些不同的事情:
>>> ClassD().method1()
I am Mixin.method1!
a
请记住,将 classes 视为实例属性的 分层搜索 space 会有所帮助!如果属性在实例本身上不存在,则沿 classes 搜索 instance.attribute
。 super()
只让您在该搜索的其余部分搜索相同的属性 space。
这允许您在子class 中实现同名方法时重用方法实现。这就是 super()
!
的重点
您的代码还有其他问题。
在查找方法时,它们绑定到它们所查找的对象。 instance.method
binds the method to instance
,这样当您调用 instance.method()
时,Python 知道将什么作为 self
传递给方法。对于 classmethod
对象,self
被替换为 type(self)
,除非您使用 ClassObject.attribute
,此时使用 ClassObject
。
因此您的 _classname
方法将 始终 为 inst1
生成 ClassC
,作为传递的 cls
对象in 是当前实例的那个。 super()
不会更改 class classmethod
在实例上访问时绑定到的内容!它会总是 type(self)
。
你也忘了在ClassB
和ClassC
__init__
方法中调用super()
,所以对于inst1
,只有ClassC.__init__
曾经实际使用过。 ClassB.__init__
和 ClassC.__init__
实现永远不会被调用。您必须在两者中添加对 super().__init__()
的调用才能发生这种情况,此时同一实例上有三个 self.attr = ...
分配,并且只有最后执行的那个才会保留。构成实例代码的每个 class 没有单独的 self
,因此没有单独的具有不同值的 self.attr
属性。
同样,那是因为 inst1.__init__()
被调用,__init__
绑定到 inst
作为 self
参数,即使您使用 super().__init__()
传递的self
仍然是inst1
.
您想要实现的是与跨所有 classes 的属性搜索完全不同的东西。打印所有 class 名称可以通过在 __mro__
上循环来完成:
class ClassA(object):
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
print(cls.__name__)
if cls is this_class:
break
class ClassB(ClassA): pass
class ClassC(ClassB): pass
然后生成:
>>> inst1 = ClassC()
>>> inst1.method2()
ClassC
ClassB
ClassA
如果你必须打印 'c'
, 'b'
, 'a'
你可以给每个 class:
添加额外的属性
class ClassA(object):
_class_attr = 'a'
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
if hasattr(cls, '_class_attr'):
print(cls._class_attr)
class ClassB(ClassA):
_class_attr = 'b'
class ClassC(ClassB):
_class_attr = 'c'
你会得到
c
b
a
印刷。
我在 Python 中有一个问题对我来说似乎非常复杂,它结合了继承、递归和 super() 函数。
首先,我用的是Python3,我的结构是深度继承的
在第一个父级 class 中,我声明了一个方法,我希望从层次结构中的每个子级 class 调用该方法,但每个子级具有不同的输入。
对我来说,使用该结构似乎非常 pythonic,它确实使我免于重复大量代码。
我的代码的简化示例如下所示:
class ClassA(object):
def __init__(self):
self.attr = 'a'
@classmethod
def _classname(cls):
return cls.__name__
def method1(self):
if self._classname() != 'ClassA': #don't call it for the very first parent class
super().method1()
print(self.attr)
class ClassB(ClassA):
def __init__(self):
self.attr = 'b'
class ClassC(ClassB):
def __init__(self):
self.attr = 'c'
inst1 = ClassC()
inst1.method1()
我希望该代码能够打印
'a'
'b'
'c'
相反,它会引发属性错误:
super().method1()
AttributeError: 'super' object has no attribute 'method1'
我知道这是一个复杂的问题,但我试图将它分解。我试图删除递归部分,但我没有得到任何改善。
根据我所做的各种尝试,我相信我已经非常接近问题的原因了,在我看来它就像一个语法问题或者那么简单的问题。
谢谢!!
那是因为您正试图在 Object_class
上访问 Method1()def method1(self):
# this will print the class name you're calling from, if you are confused.
print(self._classname())
print(self.attr)
您编写了一个 if-else,当您使用 Class C 实例化并访问 method1() 时,它会满足。
要打印 a、b、c,您需要重写每个 class.. 中的方法并从中调用 super。 但是,这仍然会有问题。因为 self 属性带有 Class C 的实例,而不是 Class B 或 A。所以,它们无法访问您在 init[=21 中初始化的属性=] 函数。
最终代码如下所示
class ClassA(object):
def __init__(self):
self.attr = 'a'
@classmethod
def _classname(cls):
return cls.__name__
def method1(self):
print(self._classname())
#don't call it for the very first parent class
#super().method1()
print(self.attr)
class ClassB(ClassA):
def __init__(self):
self.attr = 'b'
def method1(self):
print(self._classname())
print(self.attr)
super().method1()
class ClassC(ClassB):
def __init__(self):
self.attr = 'c'
def method1(self):
print(self._classname())
print(self.attr)
super().method1()
inst1 = ClassC()
inst1.method1()
恐怕您在 Python 实例和 classes 之间建立了错误的心理模型。 类 只为 'inherit' 的实例提供一系列属性,而不是单独的名称 space 实例属性。当您在实例上查找属性时,该属性不存在如果实例本身不存在,则搜索支持实例的 classes,'nearest' class 具有该属性胜过其他属性。 super()
只允许您访问具有相同名称但定义在同一分层搜索 space 的下一个 class 上的属性 space。
为了super()
正常工作,Python records what class the method1
function was defined on。这里是 ClassA
,super()
将 仅 在 ClassA
的父 class 上查找属性。在您的示例中,ClassC
和 ClassB
已被搜索,并且它们没有 method1
属性,因此正在使用 ClassA.method1
,但没有进一步的 method1
属性在其余搜索到的层中(只剩下object
,没有object.method1
)。
当 subclasses 没有重写方法时,你不需要使用 super()
,你也不能用 super()
做你想做的事。请注意 ClassB
和 ClassC
subclasses 根本没有得到该方法的副本,没有 ClassC.method1
直接属性需要考虑 ClassB.method1
存在等。同样,在实例上查找属性时发生的情况是检查实例继承层次结构中的 all class 对象的该属性,按特定顺序排列。
看看你的子classes:
>>> inst1
<__main__.ClassC object at 0x109a9dfd0>
>>> type(inst1)
<class '__main__.ClassC'>
>>> type(inst1).__mro__
(<class '__main__.ClassC'>, <class '__main__.ClassB'>, <class '__main__.ClassA'>, <class 'object'>)
__mro__
属性为您提供 ClassC
class 对象的 方法解析顺序 ;搜索属性的顺序就是这个顺序,super()
使用这个顺序进一步搜索属性。要查找 inst1.method
,Python 将逐步遍历 type(inst1).__mro__
中的每个对象,并将 return 第一个命中,因此 ClassA.method1
.
在您的示例中,您在 ClassA.method1()
定义中使用了 super()
。 Python 已将一些信息附加到该函数对象以帮助进一步搜索属性:
>>> ClassA.method1.__closure__
(<cell at 0x109a3fee8: type object at 0x7fd7f5cd5058>,)
>>> ClassA.method1.__closure__[0].cell_contents
<class '__main__.ClassA'>
>>> ClassA.method1.__closure__[0].cell_contents is ClassA
True
当您调用 super()
时,我在上面显示的闭包用于沿着 type(self).__mro__
序列开始搜索,从闭包中命名的下一个对象开始。这里有 subclass 并不重要,因此即使对于您的 inst1
对象,也将跳过所有内容,仅检查 object
:
>>> type(inst1).__mro__.index(ClassA) # where is ClassA in the sequence?
2
>>> type(inst1).__mro__[2 + 1:] # `super().method1` will only consider these objects, *past* ClassA
(<class 'object'>,)
这里不再涉及 ClassB
或 ClassC
。 MRO 依赖于当前实例的 class 层次结构,当您开始使用 多重继承 时,您可以进行彻底的更改。在层次结构中添加额外的 classes 可以改变 MRO,足以在 ClassA
和 object
之间插入一些东西:
>>> class Mixin(object):
... def method1(self):
... print("I am Mixin.method1!")
...
>>> class ClassD(ClassA, Mixin): pass
...
>>> ClassD.__mro__
(<class '__main__.ClassD'>, <class '__main__.ClassA'>, <class '__main__.Mixin'>, <class 'object'>)
>>> ClassD.__mro__[ClassD.__mro__.index(ClassA) + 1:]
(<class '__main__.Mixin'>, <class 'object'>)
ClassD
继承自 ClassA
和 继承自 Mixin
。 Mixin
也继承自 object
。 Python 跟随一些 complicated rules 将层次结构中的所有 class 放入逻辑线性顺序中,并且 Mixin
最终位于 ClassA
和 object
之间因为它继承自后者,而不是前者。
因为 Mixin
在 ClassA
之后被注入 MRO ,调用 Class().method1()
改变了 super().method1()
的行为方式,突然调用该方法会做一些不同的事情:
>>> ClassD().method1()
I am Mixin.method1!
a
请记住,将 classes 视为实例属性的 分层搜索 space 会有所帮助!如果属性在实例本身上不存在,则沿 classes 搜索 instance.attribute
。 super()
只让您在该搜索的其余部分搜索相同的属性 space。
这允许您在子class 中实现同名方法时重用方法实现。这就是 super()
!
您的代码还有其他问题。
在查找方法时,它们绑定到它们所查找的对象。
instance.method
binds the method toinstance
,这样当您调用instance.method()
时,Python 知道将什么作为self
传递给方法。对于classmethod
对象,self
被替换为type(self)
,除非您使用ClassObject.attribute
,此时使用ClassObject
。因此您的
_classname
方法将 始终 为inst1
生成ClassC
,作为传递的cls
对象in 是当前实例的那个。super()
不会更改 classclassmethod
在实例上访问时绑定到的内容!它会总是type(self)
。你也忘了在
ClassB
和ClassC
__init__
方法中调用super()
,所以对于inst1
,只有ClassC.__init__
曾经实际使用过。ClassB.__init__
和ClassC.__init__
实现永远不会被调用。您必须在两者中添加对super().__init__()
的调用才能发生这种情况,此时同一实例上有三个self.attr = ...
分配,并且只有最后执行的那个才会保留。构成实例代码的每个 class 没有单独的self
,因此没有单独的具有不同值的self.attr
属性。同样,那是因为
inst1.__init__()
被调用,__init__
绑定到inst
作为self
参数,即使您使用super().__init__()
传递的self
仍然是inst1
.
您想要实现的是与跨所有 classes 的属性搜索完全不同的东西。打印所有 class 名称可以通过在 __mro__
上循环来完成:
class ClassA(object):
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
print(cls.__name__)
if cls is this_class:
break
class ClassB(ClassA): pass
class ClassC(ClassB): pass
然后生成:
>>> inst1 = ClassC()
>>> inst1.method2()
ClassC
ClassB
ClassA
如果你必须打印 'c'
, 'b'
, 'a'
你可以给每个 class:
class ClassA(object):
_class_attr = 'a'
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
if hasattr(cls, '_class_attr'):
print(cls._class_attr)
class ClassB(ClassA):
_class_attr = 'b'
class ClassC(ClassB):
_class_attr = 'c'
你会得到
c
b
a
印刷。