Python:多重继承 和 MRO顺序(C3算法)

python存在多重继承机制,但是先说:尽量不要用多重继承。

有点多,慢慢看。。。

目录:

1. 讲多重继承前,先看看:每一代都是单继承的继承问题

2. 子类访问父类 —— super方法

3. 多重继承 --- 非super

4. 多重继承 --- super

5. MRO顺序 --- C3算法

# -------------------------------------------------------------------------------------

  • 讲多重继承前,先看看:每一代都是单继承的继承问题
class grandfather(object):
    ''' grandfather类的定义如下'''
    con_flag = 1   #普通静态字段
    __con_data = '爷爷'  #私有静态变量只能在类内使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather类初始化完成')
        
    def __get_age(self):   #私有函数只能在类内使用
        return self.age
    
    def show_age(self):
        print(self.age)
        print('grandfather年龄显示完毕')

class father(grandfather):
    ''' father类的定义如下'''
    con_flag = 2   #普通静态字段
    __con_data = '爸爸'  #私有静态变量只能在类内使用

class son(father):
    ''' son类的定义如下'''
    con_flag = 4   #普通静态字段
    __con_data = '儿子'  #私有静态变量只能在类内使用

if __name__ == '__main__':
    son1 = son('Tom',10,'001')  #实例化时,会依次查找上层父类的__init__函数,注意这个函数并不一定要有
    son1.show_age()   #子类中有该方法,则会直接执行;如果没有才去上层父类,每次单继承时这个很清晰

可见:

(1)son类继承自father类,而father类继承自grandfather类;所以son的实例对象也拥有了grandfather类的方法和属性;只不过属性/方法名称相同时,子类的属性/方法会覆盖父类。

(2)当对象调用某方法时,先在子类(本类)中查找,找不到再向上查找父类,一直向上直到找到第一个该方法出现的类。

  • 子类访问父类 —— super方法

有时子类中重写了某些方法或者属性,但是又想要使用父类的方法或者属性;或者子类单纯的想使用父类的方法或属性,可以用super方法。让我们稍微改变一下上述代码:

class grandfather(object):
    ''' grandfather类的定义如下'''
    con_flag = 1   #普通静态字段
    __con_data = '爷爷'  #私有静态变量只能在类内使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather类初始化完成')
        
    def __get_age(self):   #私有函数只能在类内使用
        return self.age
    
    def show_age(self):
        print(self.age)
        print('grandfather年龄显示完毕')

class father(grandfather):
    ''' father类的定义如下'''
    con_flag = 2   #普通静态字段
    __con_data = '爸爸'  #私有静态变量只能在类内使用

class son(father):
    ''' son类的定义如下'''
    con_flag = 4   #普通静态字段
    __con_data = '儿子'  #私有静态变量只能在类内使用

    def show_age(self):
      super(son, self).show_age()  # 显式表示调用某个类的超类,此时father类并没有show_age方法,所以继续向上查找超类,找到grandfather的方法
      super().show_age()          # super().show_age() 默认指的是当前类son的超类,用于隐式指代父类,而不用提供父类名称
      grandfather.show_age(self)  #此时必须提供self参数
     print(super().con_flag) #父类father中有该属性,于是不会继续向上查找grandfather类了
     print('son年龄显示完毕')
if __name__ == '__main__': son1 = son('Tom',10,'001') son1.show_age() #子类中有该方法,则会直接执行;如果没有才去上层,每次单继承时这个很清晰 print(son1.con_flag)

可见:

(1)子类中调用父类(多层回溯的父类)方法或属性的方式有两种:

第一种是 super().func_name() 或者 super().attr_name:例如super().show_age() 、super().con_flag;super(son, self).show_age() 这种形式可以显示指示是调用哪个类的超类 —— 可以确保多重继承时父类的方法只被执行一次。

第二种是 父类名.父类方法(self) 或者 父类名.父类属性:注意此时方法中self参数是必须的 —— 会导致多重继承时父类中的方法被多次执行,所以多重继承时最好用super方式,但是最好不要用多重继承

(2)搜索方式是:单继承比较简单,简言之就是直接依次向上层父类搜索;这个用C3算法(MRO Method Resolution Order)也可以算,后面介绍该算法

(3)super方法意义:

单继承:可以不需要父类名称就可以调用父类方法;因为父类的名称可能会变化或者调用其他父类。

多重继承:用于确保各父类只被搜索、调用一次。

(4)super原理

super(class_name, instance),它所做的事情是:

首先,获取instance的MRO顺序表:instance.__class__.mro(),例如上面的 son1.__class__.mro();

其次,查找class_name在当前MRO列表中的index,然后在instance的MRO列表上搜索class_name类的下一个类。

总结就是:super(class_name, instance)用于在 instance 的 MRO 列表上搜索 class_name类 的下一个类。

  • 多重继承 --- 非super
class grandfather(object):
    ''' grandfather类的定义如下'''
    con_flag = 1   #普通静态字段
    __con_data = '爷爷'  #私有静态变量只能在类内使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather类初始化完成')
    
    def show_age(self):
        print(self.age)
        print('grandfather年龄显示完毕')

class father(grandfather):
    ''' father类的定义如下'''
    con_flag = 2   #普通静态字段
    __con_data = '爸爸'  #私有静态变量只能在类内使用
    
    def show_age(self):
        grandfather.show_age(self)   #调用父类的方法
        print('father年龄显示完毕')

class aunt(grandfather):
    ''' aunt类的定义如下'''
    con_flag = 3   #普通静态字段
    __con_data = '姑姑'  #私有静态变量只能在类内使用

    def show_age(self):
        grandfather.show_age(self)   #调用父类的方法
        print('aunt年龄显示完毕')

class son(father,aunt):
    ''' son类的定义如下'''
    con_flag = 4   #普通静态字段
    __con_data = '儿子'  #私有静态变量只能在类内使用

    def show_age(self):
        father.show_age(self)   #子类调用父类的方法
        aunt.show_age(self)     #子类调用父类的方法
        print('son年龄显示完毕')

if __name__ == '__main__':
    son1 = son('Tom',10,'001')
    son1.show_age()   

可见:

(1)子类son调用父类father和aunt的时候,调用了2次基类grandfather的方法(有两次:grandfather年龄显示完毕);如果中间的继承关系更复杂,那么会显得更难以理解。

(2)这里的调用父类方法的方式是上述第二种,即:父类名.父类方法(self),而不是super那一种。

正因为如此,所以super方法的使用在多重继承里面更有意义。

  • 多重继承 --- super
class grandfather(object):
    ''' grandfather类的定义如下'''
    con_flag = 1   #普通静态字段
    __con_data = '爷爷'  #私有静态变量只能在类内使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather类初始化完成')
    
    def show_age(self):
        print(self.age)
        print('grandfather年龄显示完毕')
        return self.con_flag

class father(grandfather):
    ''' father类的定义如下'''
    con_flag = 2   #普通静态字段
    __con_data = '爸爸'  #私有静态变量只能在类内使用

    def show_age(self):
        s1 = super().show_age() 
        print('father年龄显示完毕')
        return s1

class aunt(grandfather):
    ''' aunt类的定义如下'''
    con_flag = 3   #普通静态字段
    __con_data = '姑姑'  #私有静态变量只能在类内使用

    def show_age(self):
        s2 = super().show_age() 
        print('aunt年龄显示完毕')
        return s2

class son(father,aunt):
    ''' son类的定义如下'''
    __con_data = '儿子'  #私有静态变量只能在类内使用

    def show_age(self):
        ss = super().show_age() 
        print('son年龄显示完毕')
        print(ss)

if __name__ == '__main__':
    son1 = son('Tom',10,'001')
    son1.show_age()   #子类中有该方法,则会直接执行;如果没有才去下一个类查找
    print(son1._son__con_data)   #本身有这个参数,所以不用回溯查找
    print(son1.__class__.mro())  #查看MRO顺序

可见:

(1)用super时,基类grandfather类只访问了一次。

(2)相同属性名称时,下层属性名(方法)会覆盖下一个类的属性名(或方法),例如con_flag属性,通过MRO顺序,son1对象获取的是father类的该属性con_flag;例如__con_data属性,在本类(son类)中存在,所以会覆盖下一个类的属性。

(3)类的搜索顺序是:[<class '__main__.son'>, <class '__main__.father'>, <class '__main__.aunt'>, <class '__main__.grandfather'>, <class 'object'>],这是通过C3算法计算出来的MRO顺序列表。

(4)之所以说下一个类而不说父类,是因为这个顺序是C3算法计算的,不是严格的继承顺序。

(5)由MRO顺序,解析一下son实例化对象son1所包含的信息:

(6)依据son1对象的信息,分析son1.show_age()的执行过程:

  • MRO顺序 --- C3算法

python官网上有详细解释,这里稍微展开一下:

1. 首先 (C1C2C3...Cn)表示一个多重继承序列,注意这个顺序很重要

2. 则 head(头)=C1,tail(尾)=(C2C3...Cn),即除了第一个类属于head之外,其他的全属于tail

3. 使用 C+(C1C2C3...Cn) = CC1C2...Cn 表示类序列的和;

4. 那么,类C的线型查找序列公式就是类C加上父类的线型查找序列和父类的线型序列的混合merge,符号表示就是:L[C(C1C2...Cn)] = C + merge(L[C1], ... ,L(Cn), C1...Cn);

5. 如果C没有父类,则L[C] = C

其规则就是:取第一个类序列的head,例如CC1C2...Cn的head就是C,如果这个head不在任何其他序列的tail里面,就把这个head加入到查找序列里面 —— 认为这是一个好head,并且从merge表达式里面去掉这个类;否则的话,就取第二个类序列,判断这个类序列的head是否合格,如果是个好head,则同样加入到查找序列,否则,继续对下一个类序列判断;直到所有类class都在merge里面被去掉,也就是全部进入查找序列;如果最后还是存在类class不能进去查找序列,则返回Exception。

示例展示:

首先类的继承关系如下:

O = object类
class F(O): pass
class E(O): pass
class D(O): pass
class C(D,F): pass
class B(D,E): pass
class A(B,C): pass

我们要算的是A的MRO顺序就是:

L[A(B(D(O),E(O)),C(D(O),F(O)))] = A + merge(L[B(D(O),E(O))], L[C(D(O),F(O))], B(D(O),E(O)) C(D(O),F(O))),这个就是根据上面的类C的线型查找序列公式

  • 简化一下就是 L[A] = A + merge(L[B], L[C], BC) 

L[B] = L[B(D(O),E(O))]  = B + merge(L[D], L[E], DE)

L[C] = L[C(D(O),F(O))] = C + merge(L[D], L[F], DF)

  • 然后继续划分:

L[D] = L[D(O)] = D + merge(L[O], O) ,由于O本身没有父类,所以L[O] = O,所以 L[D] = D + merge(O, O) = D + O =DO ----> merge(O, O)表达式中,O都在第一个位置,也就是head,所以O可以放入L[D]的查找序列中了

同理:L[E] = L[E(O)] = E + merge(L[O], O) = E + O =EO,L[F] = FO

  • 所以得到:

L[B] = B + merge(DO, EO, DE),由于此时D不是任何merge序列的tail(因为每次先从merge第一个序列开始,这里第一个就是DO序列,而DO序列的head就是D),所以D可以提出来放入B的查找序列中,并在merge中去掉D,即:L[B] = B + D +merge(O, EO, E) ,同理先判断O,由于O在EO序列的tail,所以跳到下一个序列EO,而EO序列的head是E,此时E不在任何merge序列的tail,所以E可以提出来,得到 L[B] = B + D + E +merge(O, O) = BDEO。

同理,L[C] = C + merge(DO, FO, DF) = C + D + F + O = CDFO

  • 代入L[A]得到:

L[A] = A + merge(BDEO, CDFO, BC) ,先判断BDEO的head即B,此时DEO为tail,发现B不在任何merge序列的tail,所以提出来得到:L[A] = A + B + merge(DEO, CDFO, C);

判断DEO的head即D,由于D在CDFO的tail,所以跳过,判断CDFO的head即C,发现满足条件,所以提出C,得到:L[A] = A + B + C + merge(DEO, DFO);

同理一次判断,得到:L[A] = A + B + C + D + merge(EO, FO) = A + B + C + D + E + F + O = ABCDEFO。

  • 所以类A的MRO查找序列就是ABCDEFO。

但是!!!!

MRO序列计算也有得不到想要的结果,也就是返回Exception,原文中有个例子:

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

L[C] = C + merge(L[A], L[B], AB) = C + merge(A + merge(L[X], L[Y], XY), B + merge(L[Y], L[X], YX), AB) = C + merge(A+merge(XO, YO, XY), B+merge(YO, XO, YX), AB) = C + merge(A+XYO, B+YXO)  = C + merge(AXYO, BYXO) = C + A + B + merge(XYO, YXO)

此时,merge(XYO, YXO)中的X和Y既是head又是tail,已经无法优化合并,所以会报错,Exception。

注意:以上计算中,每个继承关系中,类的继承排列顺序很重要

比较复杂吧。。。所以不必要时最好不要多重继承;例如可以用在子类中分别实例化父类来作为子类的属性,这样就可以不用多继承来实现调用父类的方法,可参考:https://baijiahao.baidu.com/s?id=1660242196992022960&wfr=spider&for=pc

例如:

# 在son中写入:可以将实例化对象作为属性放入需要的位置
self.aunt_ = aunt()
self.father_ = father()

##

参考:

https://www.cnblogs.com/szy13037-5/articles/9562639.html

https://www.cnblogs.com/silencestorm/p/8404046.html

https://blog.csdn.net/sdzhr/article/details/81084112

https://blog.51cto.com/freshair/2063290

https://www.jianshu.com/p/e188daac678c

https://www.python.org/download/releases/2.3/mro/

https://blog.csdn.net/jonstank2013/article/details/50754834

https://blog.csdn.net/zzsfqiuyigui/article/details/61672631

https://www.cnblogs.com/szy13037-5/articles/9562639.html

原文地址:https://www.cnblogs.com/qi-yuan-008/p/12845569.html