Python多重继承

前面介绍的大部分的继承都是单继承,既一个子类只有一个父类,但是Python也支持多重继承,即一个子类可以有多个父类。多继承有复杂的父类冲突问题,大部分的面向对象语言都仅仅支持单继承,Python是为数不多支持多继承的语言,本文对此展开学习。

多继承的语法结构

多继承的语法结构一般如下:

class SubClassName(BaseClass1,BaseClass2,…):
   def __init__(self, *args):
        …

当然,子类所继承的所有父类同样也能有自己的父类,这样就可以得到一个继承关系机构图如下图所示:

Python多重继承_第1张图片

父类也许很复杂,在多继承中比较难处理的是菱形的继承结构:

Python多重继承_第2张图片

我们用实例来分析多继承的情况:

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    pass

g = Grand()
g.foo()



‘’'
Base1.__init__
Base2.__init__
Base.__init__
Base1.foo
‘''

 从上面的例子可以看到:

  • 孙对象的__init__方法调用了两个父对象的__init__方法,但是只调用了一次祖父对象的__init__方法
  • 孙对象的foo方法只调用了第一个父对象的foo方法,未调用第二个父对象和祖父对象的foo方法。

甚至可以跨代混合继承:

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base):
    pass

g = Grand()
g.foo()

‘’’
Base1.__init__
Base.__init__
Base1.foo
’‘’

但是也有禁区,解释器无法区分应该使用哪个父类方法的场景:

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base, Base2):
    pass

g = Grand()
g.foo()


‘’’
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Base, Base2
’‘’

MRO

MRO(Method Resolution Order)也叫方法解析顺序。可以通过type().mro()查询类的解析顺序。

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')

class Base3(Base):
    def __init__(self):
        print(f'Base3.__init__')
        super().__init__()

    def foo(self):
        print(f'Base3.foo')

class Base31(Base1, Base2):
    pass

class Base32(Base2, Base3):
    pass

class Grand(Base32, Base31):
    pass

g = Grand()
g.foo()
print(Grand.mro())

‘’’
Base1.__init__
Base2.__init__
Base3.__init__
Base.__init__
Base1.foo
[, , , , , , , ]
’‘’

对于只支持单继承的编程语言来说,MRO 很简单,就是从当前类开始,逐个搜索它的父类。对于多继承,MRO 相对会复杂一些。Python 发展至今,经历了以下 3 种 MRO 算法,分别是:

  1. 从左往右,采用深度优先搜索(DFS)的算法,称为旧式类的 MRO;
  2. 自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化;
  3. 自 Python 2.3 版本,对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法。

旧式类MRO算法

在使用旧式类的 MRO 算法时,以下面代码为例:

class A:
    def method(self):
        print("CommonA")
class B(A):
    pass
class C(A):
    def method(self):
        print("CommonC")
class D(B, C):
    pass

D().method()

通过分析可以想到,此程序中的 4 个类是一个“菱形”继承的关系,当使用 D 类对象访问 method() 方法时,根据深度优先算法,搜索顺序为D->B->A->C->A

使用旧式类的 MRO 算法最先搜索得到的是基类 A 中的 method() 方法,即在 Python 2.x 版本中,此程序的运行结果为:

CommonA

但是,这个结果显然不是想要的,我们希望搜索到的是 C 类中的 method() 方法。

新式类MRO算法

为解决旧式类 MRO 算法存在的问题,Python 2.2 版本推出了新的计算新式类 MRO 的方法,它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。

仍以上面程序为例,通过深度优先遍历,其搜索顺序为D->B->A->C->A,由于此顺序中有 2 个 A,因此仅保留后一个,简化后得到最终的搜索顺序为D->B->C->A

新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO,也可以通过 类名.mro() 的形式,旧式类是没有 __mro__ 属性和mro() 方法的。

可以看到,这种 MRO 方式已经能够解决“菱形”继承的问题,但是可能会违反单调性原则。所谓单调性原则,是指在类存在多继承时,子类不能改变基类的 MRO 搜索顺序,否则会导致程序发生异常。

例如:

class X(object):
    pass
class Y(object):
    pass
class A(X,Y):
    pass
class B(Y,X):
    pass
class C(A, B):
    pass

通过进行深度遍历,得到搜索顺序为C->A->X->object->Y->object->B->Y->object->X->object,再进行简化(相同取后者),得到C->A->B->Y->X->object

下面来分析这样的搜索顺序是否合理,我们来看下各个类中的 MRO:

  • 对于 A,其搜索顺序为 A->X->Y->object;
  • 对于 B,其搜索顺序为 B->Y->X->object;
  • 对于 C,其搜索顺序为 C->A->B->X->Y->object。

可以看到,B 和 C 中,X、Y 的搜索顺序是相反的,也就是说,当 B 被继承时,它本身的搜索顺序发生了改变,这违反了单调性原则。

MRO C3

为解决 Python 2.2 中 MRO 所存在的问题,Python 2.3 采用了 C3 方法来确定方法解析顺序。多数情况下,如果某人提到 Python 中的 MRO,指的都是 C3 算法。C3算法解决了单调性问题和只能继承无法重写问题,具体的MRO C3算法比较复杂(The Python 2.3 Method Resolution Order | Python.org),就不在这里叙述了。

super()

Python 的内置函数 super() 用于调用父类(超类)的一个方法,用来解决多重继承问题的。不仅仅可以调用父类的构造函数,还可以调用父类的成员函数。

super() 内置函数(对象)返回一个代理对象(超类的临时对象),允许我们访问基类的方法。

class Person(object):
    def eat(self, times):
        print(f'我每天吃{times}餐。')

class Student(Person):
    def eat(self):
        # 调用超类
        super().eat(4)

tom = Student()
tom.eat()
# 我每天吃4餐。

Student.mro()
# [__main__.Student, __main__.Person, object]

调用父类初始化继承:

class Base(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

class A(Base):
    def __init__(self, a, b, c):
        super().__init__(a, b)
        #super(A, self).__init__(a, b)  # Python2 写法
        self.c = c


a = A(1,2,3)
A.mro()
# [__main__.A, __main__.Base, object]

语法

它其实是一个内置的类,语法如下:

class super(self, /, *args, **kwargs)
# or
super(type[, object-or-type])

返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。 这对于访问已在类中被重载的继承方法很有用。但特别注意,调用的是type指定的父类或兄弟类,具体调用哪一个类,取决于MRO的顺序,也就是说,实际上是调用的MRO列表上,type指定的类的下一个类,看下面的例子:

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    def __init__(self):
        print(f'Grand.__init__')
        super().__init__()

    def foo(self):
        print(f'Grand.foo')
        super(Base1, self).foo()

g = Grand()
g.foo()
print(Grand.mro())


‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
[, , , , ]
‘''

foo()函数打印的结果让你震惊,因为你写的是

super(Base1, self).foo()

但是实际输出的是

Base2.foo

而Base2与Base1其实根本就没有直接的关系,怎么会调用到Base2的foo了呢?

是因为super()根本就不看Base1的继承关系,而是看Grand的MRO,可以看到,Grand的MRO的顺序上,Base2是Base1的下一个类,而super(Base1,self).foo()调用的正是Base1下一个类的foo()方法。 

注意:如果省略第二个参数,则返回的超类对象是未绑定的。 如果第二个参数为一个对象,则 isinstance(obj, type) 必须为真值。 如果第二个参数为一个类型,则 issubclass(type2, type) 必须为真值(这适用于类方法)。

除了方法查找之外,super() 也可用于属性查找。 一个可能的应用场合是在上级或同级类中调用 描述器(任何定义了 __get__()__set__() 或 __delete__() 方法的对象)。
请注意 super() 是作为显式加点属性查找的绑定过程的一部分来实现的,例如 super().__getitem__(name)。 它做到这一点是通过实现自己的 __getattribute__() 方法,这样就能以可预测的顺序搜索类,并且支持协作多重继承。 对应地,super() 在像 super()[name] 这样使用语句或操作符进行隐式查找时则未被定义。
还要注意的是,除了零个参数的形式以外,super() 并不限于在方法内部使用。 两个参数的形式明确指定参数并进行相应的引用。 零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。

import os

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

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()
        self.__nicename__ = 'Base2'

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    def __init__(self):
        print(f'Grand.__init__')
        super().__init__()

    def foo(self):
        print(f'Grand.foo')
        super(Base1, self).foo()
        print('__getattribute__:', super().__getattribute__('__nicename__'))

g = Grand()
g.foo()
print(Grand.mro())

‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
__getattribute__: Base2
[, , , , ]
‘''

你可能感兴趣的:(python,开发语言)