Python多继承的坑与MRO C3广度优先算法

云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


前言

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有! 2 J父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重G C d , O B E {写某些方法,即覆盖父类别的` X W { i p X原有属性和方 0 E F T n !法,使其获得与父类别不同的功i e B ;能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。

有些编程语言支持多重继承,即一个子类可以同时有多_ P E o & d个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个1 ; R + ] h父类,比如Java编程语言,这时可以透过实现? a e ) F接口来实现与多重继e # B L p L承相似的效果。

现今面向对象程序设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“类型”,使得组件的类型一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在运行期动态扩展行为的遗憾。

Python的继承

Python有单V l B n i m继承与多继承。单继承即子类继承于一个类,多继承即子类继承于多个类,多继承会比较少u M B t ] X遇到,本章节主要讲单继承。

单继承

在继承中基类的构造方法(init()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。

在调用基q [ g S X . p O p类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数

Pytp d Mhon 总是首先查找对应类的3 , z方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找s h 8 ; ( , X I J调用的方法,找z W t G ~ - 7不到才去基类中找) Python单继承的案例:

class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
def call(self):
print(sA 5 R y } Welf.name, '会叫% s  k , b t')
class Cat(B * 9 AAnimal):
def __init__(self,name,age,sex):
super(Cat, self).__init__(name,age)  # 不要忘记从Animal类执行初始化
self.sex=sex
if __name__ == '__main__':2 b W G 1
c = Cat('喵喵',q @ 7 = g @ 2, K ) y'男')
c.call()
# 输出 喵喵 会叫 ,Cat继承了父类Animal的方法 

多继承

对于多继承,7 d A U一个子类可以用多个g G ^ N y c l J父类的方法,这里我们先给一个案例,再来详解如果多5 O 2 1 C H ( r个父类存在同名方法、父类中又有父类的同名方法时的执行顺序 dR Z ` aemo:

class Plant:
def __init__(self, color):
print("init Plant start")
self.color = color
print("init Plant end")
def show(self):
print("The Plant color is:", self| . - X v V.color)
class Fruit:
def __init__(self, color):
printw H f i H c("init Fruit start")
self.color = color
print("init Fruit end")
def show(self):q R F ; j
print("The Fruit color is:", self.color)
class Melon(Fruit):
def __F K 4init__(self, color):
print("init Melon stV ? ?art")
super(Mel/ l ] ^ ( Uon, self).__init__(color)
self.color = color
print("init Melon end")
def show(sels Q A ^ Uf):
print("The Melon color is:", seld $ . [ T V Cf.color)
class Mango(Fruit, Pla]  ant):
def __il  [ e |nit__(self, color):
print("init Mango start")
Plant.__init^ R n ! $ }__(self, color)
Fruit.__init__(self, color)
self.color = color
print("i/ ~ + W / ` ~ . hnit Mango end")
defR Z v A n i k show_color(self):
print("The Mango6 6 t T A V color is:", self.color)
class Watermelon(Melon, PlantK X b @ s):
def __init__(self, color):
print("init Wap z E + otermelon start"4 F 6)
Melon.__init__(self, color)
Plant.__init__(self, color)
selfJ C @ G 2 N V A.color = color
print("i? ` U p ] gnit Watermelon end")
def show_color(self):4 Q i q 6 ? e =
print("ThD x 9 $e Watermelon color is:"~ T ( d ! y [ 8 !, self.color)
if _t d P n R j_name__ == "__main__":
mango = Mango("yellow) [ 5 s")
mango.show()
watermels s %on = Watermelon("red")
waterM 2 Ymelon.show()k n ; ~ o a D 

我们定义了植物Plant类和水果Fruit类,然后定: 1 q .义瓜Melon类继承水果类,定义一个芒果Mango类继承Fruit@ W 4 4 [ D和Plant类,定义一个西瓜Watermelon类继承Melono 3 x ~ 5类和Plant类。 执行, % 8 e # P q :结果为:

init Mango start
init Plant start
init Plant endP h a 8 r K
init Fruit start
init Fruit end
init Mango end
The Fruit color is: yellow
init WaterY 5 :  a 7melon start
init Melon start
init Fruit start
init Fruit end
init Melon end
init Plant start
init Plant end
init Watermelon e h h % ) +nd
The Melon color is: ro d b Ued

$ B @ y x F _继承的方法执行顺序

从上面的案例我们可以看出:

Mango类即使初始化顺序先初始化了Plant,再初始化Frux * qit类,但是执行的同名方法的顺序仍然按定义该类是括号中的继承顺序;

Watermelon类也是按括号中的继承顺序来执行同名方法J Z 8,但是执行A 1 B +的Melon类同名方法中,即使有该执行的Melon类的父类Fruit类也有同名方法,但还是优先执行该M0 I Q V * s 9elon类,这与一般的单继承规则一致。 总结如下:

  • 定义派生类时,需要注意圆括号中4 : O O P继承父类的顺~ q o | V m B B序,若是父类中有相同的方法名,而在子类使用时未指定,pythonD 9 : ?左至右搜索 即方法在子类中未找到时,从左到右查: V P 5 5找父类中是否包含方法;
  • 如果继承的父类中,该父类还继承了别的父类,则调用同名方法时r c A e /是调用最先访问到的同名方法;
  • 支持多层父类继s - /承,子类会继承父类所有的属性和方法,包括父类的父类的所有属性 和 方法。

多继承时通过super方法初始化

单继承中我u l ~ 7 J们往往会使用super方法来初始化父类init方法,因为super方法可以防止同一个类被实例化多次。 但是这在多继承中往往会出现问题,我们把上面的例子改为如下dm y = / 8 b i Kemo:

class Plant(object):j x ( & P
def) z s n 0 __init__(self, color):
print("init Plant start")
self.color = color
print("init Plant end")
def show(self):
print("The Plant color is:= q J", selq e Hf.color)
class Fruit(object):
def __init__(self, color):
print("b Y C Finit Fruit s7 r , 7 6 ] v Mtab z @ 9 .rt+ ^ 5 Q #")
self.color = color
print("init Fruit end")
def show(self):
print("The Fruit color is:", self.color)
class Melon(Fruit):
def __iniu h ) Qt__(self, color):
print("init Melon start")
super(Melon, self).__init__(color)
self.cd S 5 H S volor = color
print("init Melon eV R 1 F  - 7nd")
def show(self):
print("The Melon color is:", self.color)
clasU R *s Mango(Fruit, Plant):
def __init__(self, color):
print("init Mango start")
super(Mango, self).__init__(color)
self.color = color
print("init Mango end")
def show_color(self):
print("The Mango color is:",Q  t self.color)
class Watermelon(Melon, Plant):
def __init__(self, color):
print("init Watermelon start")
super(Watermelo, 9 T q E qn, self).__init__(color)
self.color = color
p: Z % 8rint("init Watermelon end} M )")
def3 d l s n Z H 9 ] show_color(self):
print("The Watermelon color is:", self.color)
if __name__ == "__main__":
man- & Z B U { 6go = Mango("ye$ E Q ellow")
mango.show()
waL ? X 1 } ^termelon = Watermelon("red")
watermelg B V J i . c &on.show()

可以看到,我们只是把上面Mango类和Watermelon类初始化父类的方法改为了super方式,但是执行结果却如下:

init Mango start
init Fruit starm o K Ft
init Fruit end
init Mango end
The Fruit colo ] N Z [ 1 ? e |or is: yellowm 9 z
init Watermelon sta/ o . C c X 7 F crt
init Melon start
init Fruit start
init Fruit end
init Melon end
init Watermelon end
The Melon color is: red

可以w B A看到,两个实例中继承顺序排在后面的P, 8 S Q /lant类都没有被实例化,所以多继承中采用super是会有问题的,_ l L ; 2 P U它只会实例化一次,所以sP W i D m I ~uper类并不适合使用在多继承场合。 其实导致这个问题的原因是Python中的一个MRO(Method Resolution Order)继承调用顺序的算法,我们要修复上面的问题,可以在Fruit类中加入sup& Z S z ` M xer方法,把Fruit类改为:

class3 U n ; M H & | Fruit(object):
def __init__(self, color):
super().__init__(color)+ f i E n
print("init Frui0 ,  &  X ] M )t start")
self.color = color
print("init Fruit end")
def show(self):
print("The Fruit color is:@ F F O", self.co0 N f tlor)

就可以n 1 ; u s I F达到实例化两个类的作用了

关于Python的mro方法

Python 中针对 类 提供了一个内置属性 mro 可以查看方法搜索顺序 MRO 是 methodJ z ; * K p * resolution order,主要用于在多继承时判断 方法、属性 的调用 路径C Z E ; h 3 Python2.3以上,MRO算法就已经改为了广度优先,防止最顶级的类被多次实例化而耗费资源:

从C.mro的值可以看出, Python的方法解析优先级从高到低为u % M & , M , j

  • 实例本身(instance)
  • 类(class)
  • super class, 继承关系越近, 越先定义, 优先级R H + T E E d ~ ;越高. 例如我们执行print(Mango.__mro__)会得到如下返回: (, , , )

Python多继承的7 7 s ] p * .注意事项

在单继承场景:super().init等同于类名.init;

在多继承场景,super方法只能保证第一个父类被初始化,除s D . * v R L非每一个继承的父类都实现super初始化,最终初始化了根类Object类;

另外,子类从多个父类派生,而w J T子类又没有自己的构造函数时:

1. 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
2. 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqhd w e j.aliyun.com/l 3 J Llive

立即加M h p O ) B入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvk } onK

原文发布时间:2020-08-03
本文作者:henry_czh
本文来自:“掘金”,了解相关信息可D 6 {以关注“掘金”