python - super多继承 - super




Python的super()如何处理多重继承? (8)

我在Python面向对象编程方面非常新颖,并且我很难理解super()函数(新样式类),特别是在涉及多重继承时。

例如,如果你有类似的东西:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

我没有得到的是: Third()类会继承两个构造方法吗? 如果是,那么将使用super()运行哪一个,为什么?

如果你想运行另一个呢? 我知道这与Python方法解析顺序( MRO )有关。


总体

假设所有东西都是从object继承下来的(如果不是,那么你自己就是独立的),Python会根据你的类继承树来计算一个方法解析顺序(MRO)。 MRO满足3个属性:

  • 一个班级的孩子来到他们的父母面前
  • 左派父母来到右派父母面前
  • 一门课只在MRO中出现一次

如果不存在这样的顺序,Python错误。 这个内部工作是类祖先的C3线化。 在此处阅读全部内容: https://www.python.org/download/releases/2.3/mro/https://www.python.org/download/releases/2.3/mro/

因此,在下面的两个例子中,它是:

  1. 儿童
  2. 剩下

当一个方法被调用时,MRO中该方法的第一次出现就是被调用的方法。 任何不执行该方法的类都会被跳过。 在该方法内任何super调用都会在MRO中调用该方法的下一次出现。 因此,在继承中放置类的顺序以及将方法中的调用放入super位置都很重要。

每种方法都super优先

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child()输出:

parent
right
left
child

在每种方法中super最后

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child()输出:

child
left
right
parent

Guido本人在他的博客文章Method Resolution Order (包括两次较早的尝试)中详细说明了这一点。

在你的例子中, Third()将调用First.__init__ 。 Python会在类的父项中查找从左到右列出的每个属性。 在这种情况下,我们正在寻找__init__ 。 所以,如果你定义

class Third(First, Second):
    ...

Python首先查看First ,如果First没有该属性,那么它将查看Second

当继承开始跨越路径时,这种情况变得更加复杂(例如,如果FirstSecond继承)。 阅读上面的链接了解更多细节,但是,简而言之,Python将尝试维护每个类在继承列表中出现的顺序,从子类本身开始。

所以,举个例子,如果你有:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO将是[Fourth, Second, Third, First].

顺便说一下:如果Python找不到一致的方法解析顺序,它会引发异常,而不是回到可能让用户感到惊讶的行为。

编辑添加模棱两可的MRO示例:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Third MRO应该是[First, Second]还是[Second, First] ? 没有明显的期望,Python会引发一个错误:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

编辑:我看到一些人认为上面的例子没有super()调用,所以让我解释一下:例子的要点是展示MRO是如何构建的。 他们打算打印“第一\ nsecond \第三”或其他。 当然,你可以 - 也应该玩这个例子,添加super()调用,看看会发生什么,并且更深入地理解Python的继承模型。 但我的目标是保持简单并展示MRO的构建方式。 正如我解释的那样:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

关于@ calfzhou的评论 ,你可以像平常一样使用**kwargs

在线运行示例

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

结果:

A None
B hello
A1 6
B1 5

你也可以在位置上使用它们:

B1(5, 6, b="hello", a=None)

但你必须记住MRO,这真是令人困惑。

我可能有点讨厌,但我注意到,当人们重写一个方法时,人们每次都忘记使用*args**kwargs ,而这是对这些'魔术变量'的少数真正有用和理智的使用之一。


另一个尚未涉及的问题是传递参数来初始化类。 由于super的目的地依赖于子类,因此传递参数的唯一好方法是将它们全部打包在一起。 然后注意不要使用具有不同含义的相同参数名称。

例:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

得到:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

直接调用超类__init__以更直接地分配参数是诱人的,但如果在超类中存在任何super调用并且/或者MRO被改变并且类A可能被多次调用,则取决于实现。

总而言之:协作式继承以及用于初始化的超级特定参数不能很好地协同工作。


我想补充@Visionscaper在顶部所说的内容:

Third --> First --> object --> Second --> object

在这种情况下,解释器不会过滤出对象类,因为它是重复的,而不是它的原因,因为第二次出现在头部位置并且不出现在层次结构子集中的尾部位置。 虽然对象仅出现在尾部位置,并且在C3算法中不被视为确定优先级的强大位置。

C类的线性化(mro)L(C)是

  • C类
  • 加上合并
    • 它的父代线性化P1,P2,... = L(P1,P2,...)和
    • 其父母P1,P2的名单..

线性化合并是通过选择显示为列表头部而不是尾部的公共类来完成的,因为顺序很重要(将在下面变得清楚)

Third的线性化可以如下计算:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

因此,对于以下代码中的super()实现:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

这个方法将如何解决变得很明显

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

我明白这并不直接回答super()问题,但我觉得它足够分享。

还有一种方法可以直接调用每个继承的类:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

请注意,如果你这样做,你必须手动调用每一个,因为我很确定First__init__()不会被调用。


这是我解决问题的方法,它有多个不同的变量用于初始化,并且具有多个具有相同函数调用的MixIns。 我必须显式添加变量来传递** kwargs,并添加一个MixIn接口作为超级调用的端点。

这里A是可扩展的基类, BC是提供函数f MixIn类。 AB都希望参数v__init__C期望w 。 函数f取一个参数yQ从所有三个类继承。 MixInFBC的混合接口。


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

输出是

first 10
second 20
that's it

调用Third()查找第三个定义的init 。 并且在该例程中调用super以调用在First中定义的init 。 MRO = [第一,第二]。 现在,在First中定义的init中调用super将继续搜索MRO并找到第二个中定义的init ,并且任何对super的调用都将触发默认对象init 。 我希望这个例子能澄清这个概念。

如果你不从First调用超级。 链条停止,你会得到以下输出。

first 10
that's it




multiple-inheritance