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。这里是 ClassAsuper()ClassA 的父 class 上查找属性。在您的示例中,ClassCClassB 已被搜索,并且它们没有 method1 属性,因此正在使用 ClassA.method1,但没有进一步的 method1属性在其余搜索到的层中(只剩下object,没有object.method1)。

当 subclasses 没有重写方法时,你不需要使用 super(),你也不能用 super() 做你想做的事。请注意 ClassBClassC 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'>,)

这里不再涉及 ClassBClassC。 MRO 依赖于当前实例的 class 层次结构,当您开始使用 多重继承 时,您可以进行彻底的更改。在层次结构中添加额外的 classes 可以改变 MRO,足以在 ClassAobject 之间插入一些东西:

>>> 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 继承自 MixinMixin 也继承自 object 。 Python 跟随一些 complicated rules 将层次结构中的所有 class 放入逻辑线性顺序中,并且 Mixin 最终位于 ClassAobject 之间因为它继承自后者,而不是前者。

因为 MixinClassA 之后被注入 MRO ,调用 Class().method1() 改变了 super().method1() 的行为方式,突然调用该方法会做一些不同的事情:

>>> ClassD().method1()
I am Mixin.method1!
a

请记住,将 classes 视为实例属性的 分层搜索 space 会有所帮助!如果属性在实例本身上不存在,则沿 classes 搜索 instance.attributesuper() 只让您在该搜索的其余部分搜索相同的属性 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)

  • 你也忘了在ClassBClassC__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

印刷。