Python super() 无法调用父级继承的方法

Xar*_*Kor 4 python inheritance super

我在 Python 中有一个对我来说似乎很复杂的问题,它结合了继承、递归和 super() 函数。

首先,我使用的是Python 3,我有一个深度继承的结构。

在第一个父类中,我声明了一个方法,我希望从层次结构中的每个子类调用该方法,但每个子类都有不同的输入。

对我来说,这种结构的使用似乎非常 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()
Run Code Online (Sandbox Code Playgroud)

我希望该代码能够打印

'a'
'b'
'c'
Run Code Online (Sandbox Code Playgroud)

相反,它引发了一个属性错误:

super().method1()
AttributeError: 'super' object has no attribute 'method1'
Run Code Online (Sandbox Code Playgroud)

我知道这是一个复杂的问题,但我试图将其分解。我试图删除递归部分,但我没有得到任何改善。

基于我所做的各种尝试,我相信我非常接近问题的原因,在我看来就像是语法问题或那么简单的事情。

谢谢!!

Mar*_*ers 6

恐怕您对 Python 实例和类的关系建立了错误的思维模型。类只为实例提供一系列要“继承”的属性,而不是为实例属性提供单独的名称空间。支持实例的类,具有该属性的“最近”类胜过其他类。super()只是让您访问具有相同名称但在同一分层搜索空间中的下一个类上定义的属性。

为了super()正常工作,Python 会记录method1函数在 上定义的类。这里说的ClassA,并且super()找到的父类的属性ClassA。在您的示例中,ClassC并且ClassB已经被搜索过并且它们没有method1属性,因此ClassA.method1正在使用,但是method1在搜索的其余图层中没有其他属性(仅object保留,并且没有object.method1)。

super()当子类没有覆盖方法时,您不需要使用,也不能用任何方式做您想做的事情super()。请注意,ClassBClassC子类根本没有获得方法的副本,没有ClassC.method1需要考虑ClassB.method1存在的直接属性等。 同样,在实例上查找属性时会发生什么是继承中的所有类对象实例的层次结构以特定顺序检查该属性。

看看你的子类:

>>> 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'>)
Run Code Online (Sandbox Code Playgroud)

__mro__属性为您提供了方法解析顺序您的ClassC类对象; 正是这个顺序搜索属性,并super()用于进一步搜索属性。为了 find inst1.method,Python 将遍历其中的每个对象type(inst1).__mro__并返回第一个命中,因此ClassA.method1.

在您的示例中,您super()ClassA.method1()定义中使用了。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
Run Code Online (Sandbox Code Playgroud)

当你调用super()我上面展示的闭包时,它用于沿着type(self).__mro__序列开始搜索,从在闭包中命名的对象之后的下一个对象开始。这里是否有子类并不重要,因此即使对于您的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'>,)
Run Code Online (Sandbox Code Playgroud)

在任何时候都不再ClassBClassC参与在这里。MRO 取决于当前实例的类层次结构,您可以在开始使用多继承时进行彻底的更改。将额外的类添加到层次结构中可以改变 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'>)
Run Code Online (Sandbox Code Playgroud)

ClassD继承自ClassA Mixin. Mixin从继承object 。Python 遵循一些复杂的规则将层次结构中的所有类按逻辑线性顺序排列,并Mixin最终介于ClassAobject因为它继承自后者而不是前者。

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

>>> ClassD().method1()
I am Mixin.method1!
a
Run Code Online (Sandbox Code Playgroud)

请记住,将类视为实例属性的分层搜索空间会有所帮助!instance.attribute如果属性不存在于实例本身,则沿着类搜索。super()只需让您沿着该搜索空间的其余部分搜索相同的属性即可。

这使您可以在子类中实现具有相同名称的方法时重用方法实现。这就是重点super()

您的代码还有其他问题。

  • 查找方法时,它们绑定到它们查找的对象instance.method 将方法绑定到instance,以便在调用时instance.method(),Python 知道将什么作为 传递给方法self。对于classmethod对象,self被替换为type(self),除非你做ClassObject.attribute了,此时ClassObject使用。

    因此,您的_classname方法将始终生成ClassCfor inst1,因为cls传入的对象是当前实例的对象。在实例上访问时super(),不会更改 classclassmethod绑定到的内容!这将永远type(self)

  • 你也忘了打电话给super()ClassBClassC __init__方法,所以inst1,只能ClassC.__init__是永远实际使用。该ClassB.__init__ClassC.__init__实现永远不会被调用。您必须super().__init__()在两者中添加一个调用才能发生这种情况,此时self.attr = ...同一实例上有三个赋值,并且只有最后执行的一个将保留。没有单独self为每个弥补实例的代码的类的,所以没有单独的self.attr具有不同值的属性。

    再次,这是因为inst1.__init__()被调用时,__init__势必会instself参数,即使你使用super().__init__()self是遗体上传递inst1

您想要实现的目标与跨所有类的属性搜索完全不同。打印所有类名可以通过循环来完成__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
Run Code Online (Sandbox Code Playgroud)

这将产生:

>>> inst1 = ClassC()
>>> inst1.method2()
ClassC
ClassB
ClassA
Run Code Online (Sandbox Code Playgroud)

如果必须打印'c', 'b''a'则可以为每个类添加额外的属性:

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'
Run Code Online (Sandbox Code Playgroud)

你会得到

c
b
a
Run Code Online (Sandbox Code Playgroud)

打印。