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

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

有点多,慢慢看。。。

目录:

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

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

3. 多重继承 --- 非super

4. 多重继承 --- super

5. MRO顺序 --- C3算法

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

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

classgrandfather(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): #私有函数只能在类内使用

returnself.agedefshow_age(self):print(self.age)print('grandfather年龄显示完毕')classfather(grandfather):'''father类的定义如下'''con_flag= 2 #普通静态字段

__con_data = '爸爸' #私有静态变量只能在类内使用

classson(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方法。让我们稍微改变一下上述代码:

classgrandfather(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): #私有函数只能在类内使用

returnself.agedefshow_age(self):print(self.age)print('grandfather年龄显示完毕')classfather(grandfather):'''father类的定义如下'''con_flag= 2 #普通静态字段

__con_data = '爸爸' #私有静态变量只能在类内使用

classson(father):'''son类的定义如下'''con_flag= 4 #普通静态字段

__con_data = '儿子' #私有静态变量只能在类内使用

defshow_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方式,但是最好不要用多重继承。

(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

classgrandfather(object):'''grandfather类的定义如下'''con_flag= 1 #普通静态字段

__con_data = '爷爷' #私有静态变量只能在类内使用

def __init__(self,name,age,id_):

self.name=name

self.age=age

self.__id =id_print('grandfather类初始化完成')defshow_age(self):print(self.age)print('grandfather年龄显示完毕')classfather(grandfather):'''father类的定义如下'''con_flag= 2 #普通静态字段

__con_data = '爸爸' #私有静态变量只能在类内使用

defshow_age(self):

grandfather.show_age(self) #调用父类的方法print('father年龄显示完毕')classaunt(grandfather):'''aunt类的定义如下'''con_flag= 3 #普通静态字段

__con_data = '姑姑' #私有静态变量只能在类内使用

defshow_age(self):

grandfather.show_age(self) #调用父类的方法print('aunt年龄显示完毕')classson(father,aunt):'''son类的定义如下'''con_flag= 4 #普通静态字段

__con_data = '儿子' #私有静态变量只能在类内使用

defshow_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

classgrandfather(object):'''grandfather类的定义如下'''con_flag= 1 #普通静态字段

__con_data = '爷爷' #私有静态变量只能在类内使用

def __init__(self,name,age,id_):

self.name=name

self.age=age

self.__id =id_print('grandfather类初始化完成')defshow_age(self):print(self.age)print('grandfather年龄显示完毕')returnself.con_flagclassfather(grandfather):'''father类的定义如下'''con_flag= 2 #普通静态字段

__con_data = '爸爸' #私有静态变量只能在类内使用

defshow_age(self):

s1=super().show_age()print('father年龄显示完毕')returns1classaunt(grandfather):'''aunt类的定义如下'''con_flag= 3 #普通静态字段

__con_data = '姑姑' #私有静态变量只能在类内使用

defshow_age(self):

s2=super().show_age()print('aunt年龄显示完毕')returns2classson(father,aunt):'''son类的定义如下'''

__con_data = '儿子' #私有静态变量只能在类内使用

defshow_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)类的搜索顺序是:[, , , , ],这是通过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()

##

参考:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值