天天看点

了解Python中的多重继承

文章目录

        • 1. 钻石问题
        • 2. MRO
        • 3. Super
          • 3.1 前世
          • 3.2 今生
        • 参考文献

    人们普遍认为,多重继承是危险的或不好的,这主要是由于编程语言的多重继承机制执行不当,尤其是由于使用不当而引起的,Java甚至不支持多重继承,而C++支持。Python具有复杂且精心设计的多重继承方法。

    在类定义中,当一个名为SubClassName的子类从BaseClass1,BaseClass2,BaseClass3这样的父类继承时,具体格式如下所示:

class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...):
    pass
           

    显然,父类BaseClass1,BaseClass2,BaseClass3能从其他父类继承,于是我们可以得到一课继承树:

了解Python中的多重继承

1. 钻石问题

    “钻石问题”(有时被称为“致命的死亡钻石”)是一个常用术语,当两个类B和C继承自父类A,而另一个类D继承自B和C时,会产生歧义。如果A中有一个方法“m”被B或C(甚至两者)重写,那么D继承该方法的哪个版本?可能是A、B或C。

了解Python中的多重继承

    第一个钻石问题的配置是这样的:B和C都覆盖A的方法m。代码如下:

class A:
    def m(self):
        print("m of A called")

class B(A):
    def m(self):
        print("m of B called")
    
class C(A):
    def m(self):
        print("m of C called")

class D(B,C):
    pass

d=D()
d.m()
           

    在本例中,d为D类的实例,调用d.m()方法,输出如下:

m of B called
           

    如果D类头部变为" class D(C,B) “,输出为” m of C called "。

2. MRO

    如果方法m被B或C其中一个类重写,例如在C类中被重写:

class A:
    def m(self):
        print("m of A called")

class B(A):
    pass

class C(A):
    def m(self):
        print("m of C called")

class D(B,C):
    pass

d=D()
d.m()
           

    在python2和python3的输出结果是不同的:

#Python2输出结果
m of A called

#Python3输出结果
m of C called
           

    为了在Python2中拥有与Python3相同的继承行为,每个类都必须从object类继承。上例中的类A不是从object继承的,所以如果我们用python2调用脚本,就会得到一个所谓的旧样式类。与旧样式类相对,新样式类的第一个父类继承自Python根级别的object类,从格式上看,新旧样式类区别如下:

# Old style class 
class OldStyleClass:  
    pass
  
# New style class 
class NewStyleClass(object):  
    pass
           

    两种声明样式中的方法解析顺序(MRO)不同。旧样式类使用DLR算法,而新样式类使用C3线性化算法(C3 Linearization algorithm)来实现多种继承的方法解析。

    旧样式类的多重继承由两个规则控制:深度优先,然后从左到右。在上例中,DLR算法的解析顺序将为D,B,A,C,A,但是,A不能两次出现,因此顺序将为D,B,A,C。如果将A的定义头部更改为" class A(object): ",那么在这两个Python版本中,将得到相同的结果。

    C3线性化算法用于消除DLR算法产生的不一致性,具体计算过程我没看,也不懂,所以就不写了。个人总结MRO的排序遵循以下原则:

  • 子类优先于父类,子类全部排序完才会继续检查父类
  • 如果一个子类继承自多个父类,则父类将按照子类定义中出现的顺序进行排序

    通过使用__mro__属性或mro方法,可以显示类的MRO顺序。示例如下:

class A():
    def m(self):
        print("m of A called")

class B(A):
    pass

class C(A):
    def m(self):
        print("m of C called")

class D(B,C):
    pass

print(D.__mro__)
           

    在python3的环境下执行得到MRO的顺序为:

3. Super

3.1 前世

    将前面的示例稍加改动,保存为test.py文件:

class A:
    def m(self):
        print("m of A called")

class B(A):
    def m(self):
        print("m of B called")
        A.m(self)
    
class C(A):
    def m(self):
        print("m of C called")
        A.m(self)

class D(B,C):
    def m(self):
        print("m of D called")
        B.m(self)
        C.m(self)
           

    在命令行创建对象:

>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of A called
m of C called
m of A called
           

    这里A类的方法m被反复调用了两次,如果修改为只调用一次,有两种思路:一种是将B和C类的m方法分情况编写;一种是使用super()函数。第一种方法不符合python的风格,代码如下:

class A:
    def m(self):
        print("m of A called")

class B(A):
    def _m(self):
        print("m of B called")
    def m(self):
        self._m()
        A.m(self)
    
class C(A):
    def _m(self):
        print("m of C called")
    def m(self):
        self._m()
        A.m(self)

class D(B,C):
    def m(self):
        print("m of D called")
        B._m(self)
        C._m(self)
        A.m(self)
           
3.2 今生

    第二种方法使用super(),很pythonic:

class A:
    def m(self):
        print("m of A called")

class B(A):
    def m(self):
        print("m of B called")
        super().m()
    
class C(A):
    def m(self):
        print("m of C called")
        super().m()

class D(B,C):
    def m(self):
        print("m of D called")
        super().m()
           

    两种方法输出一致:

>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
           

    上面描述的是super函数的第一个用途,调用父类方法。super函数的第二个用途是:确保父类被正确初始化。当实例使用__init__方法初始化时,经常会使用super函数,示例如下:

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        print('A1.__init__')
        super().__init__()
        print('A2.__init__')

class B(Base):
    def __init__(self):
        print('B1.__init__')
        super().__init__()
        print('B2.__init__')

class C(A,B):
    def __init__(self):
        print('C1.__init__')
        super().__init__()  # Only one call to super() here
        print('C2.__init__')
           

    将代码保存为test.py,创建对象实例,执行结果为:

>>> from test import * 
>>> c=C()
C1.__init__
A1.__init__
B1.__init__
Base.__init__
B2.__init__
A2.__init__
C2.__init__
>>> b=B()
B1.__init__
Base.__init__
B2.__init__
>>> a=A()
A1.__init__
Base.__init__
A2.__init__
           

    从结果可以看出,super()的本质是函数,其行为也符合函数嵌套的执行顺序。

    查询各类的mro顺序如下:

>>> C.mro()
[<class 'test.C'>, <class 'test.A'>, <class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> B.mro()
[<class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> A.mro()
[<class 'test.A'>, <class 'test.Base'>, <class 'object'>]
>>> Base.mro()
[<class 'test.Base'>, <class 'object'>]
           

参考文献

https://www.programiz.com/python-programming/methods/built-in/super

https://www.programiz.com/python-programming/multiple-inheritance

https://www.geeksforgeeks.org/method-resolution-order-in-python-inheritance/?ref=rp

https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html

https://www.python-course.eu/python3_multiple_inheritance.php