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

时间:2018-06-03 18:52:58

标签: 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()

我希望打印代码

'a'
'b'
'c'

相反,它会引发属性错误:

super().method1()
AttributeError: 'super' object has no attribute 'method1'

我知道这是一个复杂的问题,但我试图将它分开。我试图删除递归部分,但我没有更好。

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

谢谢!

2 个答案:

答案 0 :(得分:2)

我担心你在Python实例和类的关系方面建立了错误的心理模型。类只为实例提供了一系列属性来继承,而不是为了存在的实例属性而单独命名空间。当你在实例上查找属性并且属性不存在于实例上时本身,搜索支持实例的类,其中最近的'具有该属性的类赢得其他人。 super()只允许您访问具有相同名称但在同一分层搜索空间中的下一个类上定义的属性。

为了让super()正常工作,Python records what class the method1 function was defined on。此处,ClassAsuper()只会 ClassA的父类中查找属性。在您的示例中,ClassCClassB已经被搜索过,但他们没有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'>)

__mro__属性为您提供ClassC类对象的方法解析顺序;正是这个顺序搜索了属性,而super()用来进一步搜索属性。要查找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

当您致电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'>,)

此处不再涉及ClassBClassC。 MRO依赖于当前实例的类层次结构,当您开始使用多重继承时,您可以进行彻底的更改。在层次结构中添加额外的类可以改变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的{​​{1}} Mixin也继承自Mixin 。 Python遵循一些complicated rules将层次结构中的所有类放入逻辑线性顺序,object最终在MixinClassA之间,因为它继承自后者,而不是前者。

由于objectMixin后注入MRO ,因此调用ClassA会更改Class().method1()的行为方式,并突然调用该方法不同的东西:

super().method1()

请记住,将类视为实例属性的分层搜索空间会有所帮助!如果属性在实例本身上不存在,则会在类中搜索>>> ClassD().method1() I am Mixin.method1! a instance.attribute只是让您在该搜索空间的其余部分搜索相同的属性。

这允许您在子类中实现具有相同名称的方法时重用方法实现。这就是super()的全部内容!

您的代码还有其他问题。

  • 查找方法时,它们绑定到查找它们的对象super() binds the method to instance,因此当您调用instance.method时,Python会知道将哪些内容作为instance.method()传递给方法。对于self个对象,classmethod将替换为self,除非您使用了type(self),此时使用了ClassObject.attribute

    因此,ClassObject方法总是_classname生成ClassC,因为传入的inst1对象是当前实例。 cls在实例上访问时不会更改绑定的super()类! 始终

  • 您也忘了使用classmethodtype(self) super()方法拨打ClassB,因此对于ClassC,只有__init__实际使用过。永远不会调用inst1ClassC.__init__实现。您必须在两者中添加对ClassB.__init__的调用才能实现,此时在同一实例上有三个ClassC.__init__分配,并且只保留最后执行的一个super().__init__()分配。对于构成实例代码的每个类,没有单独的self.attr = ...,因此没有单独的self属性具有不同的值。

    同样,因为self.attr被调用,inst1.__init__()被绑定到__init__ inst参数,即使您使用了self传递的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

如果您需要打印>>> inst1 = ClassC() >>> inst1.method2() ClassC ClassB ClassA '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'

打印。

答案 1 :(得分:0)

那是因为你试图访问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,当你用C类和访问方法1()进行实例化时,它会满足。

要打印a,b,c,您需要覆盖每个类中的方法..并从中调用super。 但是,这仍然会有问题。因为self属性包含C类实例,而不是B类或A类。因此,它们无法访问您在 init func中初始化的属性。

最终代码如下所示

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()