Python的super()如何与多重继承一起使用?

喜你入骨 提交于 2019-12-18 17:32:32

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

我在面向对象的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 )有关。


#1楼

您的代码和其他答案都是错误的。 他们缺少合作子类正常工作所需的前两个类中的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("third")

super()调用在每个步骤中都会在MRO中找到下一个方法,这就是为什么First和Second也必须拥有它的原因,否则执行将在Second.__init__()的末尾停止。

这是我得到的:

>>> Third()
second
first
third

#2楼

我知道这并不能直接回答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__()


#3楼

另一个尚未涵盖的要点是传递用于初始化类的参数。 由于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可能被多次调用(取决于实现),则失败。

得出的结论是:协作继承以及用于初始化的超级参数和特定参数不能很好地协同工作。


#4楼

这就是我解决以下问题的方式:具有用于初始化的不同变量的多个继承以及具有相同函数调用的多个MixIn。 我必须显式地将变量添加到传递的** kwargs中,并添加一个MixIn接口作为超级调用的端点。

这里A是可扩展的基类,而BC是MixIn类,它们都提供函数fAB都在其__init__期望参数v ,而C期望w 。 函数f采用一个参数yQ从所有三个类继承。 MixInFBC的mixin接口。


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)

#5楼

我想一点点地阐述一下答案,因为当我开始阅读如何在Python的多继承层次结构中使用super()时,我没有立即得到它。

您需要了解的是, super(MyClass, self).__init__()根据完整继承层次结构中使用的方法解析顺序(MRO)算法提供了下一个 __init__方法。

最后一部分对于理解至关重要。 让我们再次考虑示例:

#!/usr/bin/env python2

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

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

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

根据 Guido van Rossum 撰写的有关方法解析顺序文章 ,在python 2.3之前,使用“深度优先从左到右遍历”来计算__init__解析顺序:

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

除去所有重复项(最后一个重复项除外)之后,我们得到:

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

因此,让我们追踪实例化Third类的实例时发生的情况,例如x = Third()

  1. 根据MRO Third.__init__执行。
    • 打印Third(): entering
    • 然后执行super(Third, self).__init__()并MRO返回First.__init__ ,这将被调用。
  2. First.__init__执行。
    • 打印First(): entering
    • 然后执行super(First, self).__init__()并MRO返回Second.__init__ ,这将被调用。
  3. Second.__init__执行。
    • 打印Second(): entering
    • 然后super(Second, self).__init__()执行,MRO返回被调用的object.__init__
  4. object.__init__执行(那里的代码中没有打印语句)
  5. 执行返回到Second.__init__ ,然后显示Second(): exiting
  6. 执行返回到First.__init__ ,然后显示First(): exiting
  7. 执行返回到Third.__init__ ,然后显示Third(): exiting

这详细说明了为什么实例化Third()导致:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

MRO算法已从Python 2.3开始进行了改进,可以在复杂的情况下很好地工作,但是我猜想在大多数情况下,仍然可以使用“深度优先的从左到右遍历” +“删除重复的期望值”(请如果不是这种情况,请发表评论)。 请务必阅读Guido的博客文章!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!