多重继承和MRO

Last Updated: 2023-11-07 11:24:34 Tuesday

-- TOC --

貌似有人说过,OOP编程是区分程序员水平的一个标志。我个人认为,熟练掌握OOP编程思想和技巧,是能够驾驭更加庞大代码量的前提,也是能够更好地对问题进行抽象的基础。

多重继承

继承就是继承,将父类的变量和方法都变成自己的。多重继承就是有多个父类。这种结构很强大,用好了可以减少很多重复代码,但也非常容易导致错误。

示例:

class A:
  def __init__(self):
    print("Enter A")
    print("Leave A")

class B(A):
  def __init__(self):
    print("Enter B")
    A.__init__(self)
    print("Leave B")

class C(A):
  def __init__(self):
    print("Enter C")
    A.__init__(self)
    print("Leave C")

class D(A):
  def __init__(self):
    print("Enter D")
    A.__init__(self)
    print("Leave D")

class E(B, C, D):
  def __init__(self):
    print("Enter E")
    B.__init__(self)
    C.__init__(self)
    D.__init__(self)
    print("Leave E")

E()

A是所有类型的父类,BCD都继承自A,E继承BCD。他们都重写了__init__方法,而且都直接调用父类的__init__函数,执行效果如下:

D:\py>python super.py
Enter E
Enter B
Enter A
Leave A
Leave B
Enter C
Enter A
Leave A
Leave C
Enter D
Enter A
Leave A
Leave D
Leave E

问题很明显,A的__init__函数被执行了多次!

解决问题的方法,就是调用super()接口:

class A:
  def __init__(self):
    print("Enter A")
    print("Leave A")

class B(A):
  def __init__(self):
    print("Enter B")
    super().__init__()
    print("Leave B")

class C(A):
  def __init__(self):
    print("Enter C")
    super().__init__()
    print("Leave C")

class D(A):
  def __init__(self):
    print("Enter D")
    super().__init__()
    print("Leave D")

class E(B, C, D):
  def __init__(self):
    print("Enter E")
    super().__init__()
    print("Leave E")

E()

继承关系不变,BCDE的__init__函数都采用super()函数来调用父类的__init__。执行效果如下:

D:\py>python super.py
Enter E
Enter B
Enter C
Enter D
Enter A
Leave A
Leave D
Leave C
Leave B
Leave E

E的初始化,不再重复调用A了!

使用super时,可以保证公共的父类接口仅被执行一次,至于执行的顺序,是按照MRO(Method Resolution Order)方法解析顺序进行的。简单地说,super会按照mro顺序查找出一个method来执行。

在Python中,super接口的调用不再需要参数,需要参数的是Python2的写法。

MRO

MRO,Method Resolution Order,应该是一个比较通用的概念,具体是指在有继承关系,特别是多重继承关系的OOP代码中,子类的对象在调用继承下来的函数时,要通过MRO的顺序,去定位具体的调用位置。

class aa:
    def __init__(self):
        print('in aa')

    def fun(self):
        print('in aa fun')


class bb:
    def __init__(self):
        print('in bb')

    def fun(self):
        print('in bb fun')


class cc(aa,bb):
    pass



c = cc()
c.fun()
print(cc.mro())

cc继承aa和bb,因此cc的__init__和fun函数,都需要MRO来决策具体的调用位置,因为这两个函数,在aa和bb中都存在。这段代码的运行效果如下:

in aa
in aa fun
[<class '__main__.cc'>, <class '__main__.aa'>, <class '__main__.bb'>, <class 'object'>]

按照MRO的顺序,cc的两次调用,都定位在aa内。

MRO是把类的继承关系线性化的一个过程,而线性化的方式决定了程序运行过程中具体会调用哪个方法。

Python曾经至少有三种不同的MRO:

cc.mro()输出类cc的MRO顺序list!

MRO顺序,其实就是代码中的书写顺序,aa写在bb前面,MRO中,aa就在bb前面!

与MRO密切相关的,是一个我们常见的super函数

MRO满足两个性质:(1)本地优先;(2)单调性;

本地优先:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。

单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。

阅读代码的过程中,看到另一种调用super按照MRO查找调用点的case:

class aa:
    def __init__(self):
        print('in aa')

    def fun(self):
        print('in aa fun')

    def one(self):
        super().one()
        print('one in aa')


class bb:
    def __init__(self):
        print('in bb')

    def fun(self):
        print('in bb fun')

    def one(self):
        print('one in bb')


class cc(aa,bb):
    pass



c = cc()
c.one()
print(cc.mro())
print(aa.mro())
print(bb.mro())

执行c.one(),按照mro的顺序,会调用aa.one,但是aa.one调用了super,aa本身并没有父类!这个super调到哪里去了?执行效果:

$ python3 test_class.py
in aa
one in bb
one in aa
[<class '__main__.cc'>, <class '__main__.aa'>, <class '__main__.bb'>, <class 'object'>]
[<class '__main__.aa'>, <class 'object'>]
[<class '__main__.bb'>, <class 'object'>]

显然,aa.one中的super,调用的是bb.one。还是用mro来解释,这条查找链是从cc开始的,在aa.one中执行super,按照查找连,自然会找到bb.one来执行。

__subclasses__()获取所有子类

$ cat t1.py
class A():
    ...

class B(A):
    ...

class C(A):
    ...

for cls in A.__subclasses__():
    print(cls, cls.__name__)
$ python3 t1.py
<class '__main__.B'> B
<class '__main__.C'> C

我一直疑惑的unittest模块,它是如何找到unittest.TestCase的子类的?应该就是这个方法。

__bases__获取父类

$ cat t2.py
class A():
    ...

class B(A):
    ...

class C(A):
    ...

class D(B,C):
    ...

print(B.__bases__)
print(C.__bases__)
for cls in D.__bases__:
    print(cls.__name__)
$ python3 t2.py
(<class '__main__.A'>,)
(<class '__main__.A'>,)
B
C

issubclass和isinstance

issubclass,判断某个类是否是另一个类的子类;

isinstance,判断某个对象是否是属于某个类的实例;

$ cat t3.py
class A():
    ...

class B(A):
    ...

class C(A):
    ...

class D(B,C):
    ...

print(issubclass(B,A))
print(issubclass(D,A))

b1 = B()
d1 = D()
print(isinstance(b1,D))
print(isinstance(d1,D))
$ python3 t3.py
True
True
False
True

本文链接:https://cs.pynote.net/sf/python/202204022/

-- EOF --

-- MORE --