了解Python中的多重继承

    人们普遍认为,多重继承是危险的或不好的,这主要是由于编程语言的多重继承机制执行不当,尤其是由于使用不当而引起的,Java甚至不支持多重继承,而C++支持。Python具有复杂且精心设计的多重继承方法。
    在类定义中,当一个名为SubClassName的子类从BaseClass1,BaseClass2,BaseClass3这样的父类继承时,具体格式如下所示:

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

    显然,父类BaseClass1,BaseClass2,BaseClass3能从其他父类继承,于是我们可以得到一课继承树:
在这里插入图片描述

1. 钻石问题

    “钻石问题”(有时被称为“致命的死亡钻石”)是一个常用术语,当两个类B和C继承自父类A,而另一个类D继承自B和C时,会产生歧义。如果A中有一个方法“m”被B或C(甚至两者)重写,那么D继承该方法的哪个版本?可能是A、B或C。
在这里插入图片描述
    第一个钻石问题的配置是这样的: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的顺序为:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值