首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

一切皆对象——Python面向对象:多重继承与MRO

多重继承

Python同C++一样,允许多重继承。写法上也十分简洁:

classA:pass

classB:pass

classC(A,B):

pass

print(C.__bases__)

# (, )

这样,类和的属性就被继承了。另外还存在一种多级继承的概念:

classA:pass

classB(A):pass

classC(B):pass

层级关系可以写作:。

下面来看这样一个问题。假如有一个方法在和中都定义了,在中没有定义,那么的对象调用该方法时调用的是谁的方法?

classA:

defm(self):

print('I\'m A')

classB:

defm(self):

print("I'm B")

classC(A,B):

pass

c=C()

c.m()

# I'm A

改变一下继承顺序再看一看:

classC(B,A):

pass

c=C()

c.m()

# I'm B

好像能得到一个初步结论了,谁声明在前面就调用谁,很像广度优先算法(Breath-first Search)

如果是这样的关系呢:

classA:

defm(self):

print('I\'m A')

classB:

defm(self):

print("I'm B")

classC(A):

pass

classD(B):

defm(self):

print("I'm D")

classE(C,D):

pass

classF(D,C):

pass

e=E()

f=F()

e.m()

# I'm A

f.m()

# I'm D

我们利用图来看一下他们的关系(图中粉色线表示第二顺位):

从上面的运行结果来看,寻找方法的算法很像是深度优先算法(DFS,Depth-first Search)。实际上,Python中方法的查找算法称作C3算法,查找的过程称作方法解析顺序(MRO,Method Resolution Order)

MRO

关于详细的C3算法可以自行查找。这里只说明其中所蕴含的两个问题:

单调性问题

无效重写问题

采用C3算法的原因即在于它解决了上述两个问题而无论BFS或DFS都无法完美解决。

单调性问题

如上图的继承关系,当的对象调用方法时,要求先在的父类中搜索后再去分支中搜索。这是合理的,因为的这一条独立的继承序列还没有搜索完毕。搜索顺序是:。显然,BFS无法解决这个问题,而DFS可以。

无效重写问题

对于上图的继承关系(菱形继承问题),当的对象调用方法时,要求先在的所有子类中搜索完毕后再搜索。这样能够保证子类中重写的方法可以被搜索到。如果按照DFS来做,那么中重写的将被截胡,永远不会被调用到,重写就没有意义了。这时候则需要BFS方法来做。搜索顺序为:。C3算法解决了上面两个问题。我们可以通过类的属性或方法来查看它的MRO顺序:

# 上面的E和F

frompprintimportpprint

pprint(E.__mro__)

# (,

# ,

# ,

# ,

# ,

# )

pprint(F.mro())

# [,

# ,

# ,

# ,

# ,

# ]

不论方法或属性,都是遵从上述顺序进行搜索调用的。简单总结起来大致有3点:1)子类优先;2)声明顺序优先;3)单调性

当我们尝试写出一种C3算法无解的继承方式时,Python会报错:

classA:pass

classB(A):pass

classC(A,B):pass

# TypeError: Cannot create a

# consistent method resolution

# order (MRO) for bases A, B

这是因为,按照子类优先原则,的搜索顺序应当位于之前,然而按照声明顺序,却应当在的前面。这样,Python无法给出一个准确的MRO,因而报错。所以,虽然Python支持多重继承,使用不当会导致程序复杂度火箭式上升。

super

在单继承中,可以调用父类的方法:

classA:

defm(self):

print('A.m')

classB(A):

defm(self):

super().m()

print('B.m')

b=B()

b.m()

# A.m

# B.m

如果在多重继承中,调用的又是哪个父类的方法呢?答案是该类MRO的前一个类的方法:

classA:

defm(self):

print('I\'m A')

classB:

defm(self):

print("I'm B")

classC(A):

defm(self):

super().m()

print('I\'m C')

classD(B,A):

defm(self):

print("I'm D")

classE(C,D):

defm(self):

super().m()

print('I\'m E')

classF(D,C):

defm(self):

super().m()

print('I\'m F')

e=E()

f=F()

pprint(E.mro())

# [,

# ,

# ,

# ,

# ,

# ]

e.m()

# I'm D

# I'm C

# I'm E

pprint(F.mro())

# [,

# ,

# ,

# ,

# ,

# ]

f.m()

# I'm D

# I'm F

另外一个关键点在于,调用链会在第一个没有使用的类中断掉。例如上面中没有导致的方法没有被调用。这里引出了我们函数的真正意义所在:保证多重继承的灵活性。试想,当我们实现了一个类希望给别人使用时,其他人很可能为了增加某些功能而混入了其他类,如果我们自己的类中没有使用来初始化父类的话,使用我们类的人只能通过硬编码的方式来编写他们的代码:

# 我们的类

classMy:

def__init__(self):

self.a=1

# 其他人使用

classInject:

def__init__(self):

super().__init__()

self.b=2

classSon(My,Inject):

def__init__(self):

super().__init__()

s=Son()

print(s.b)

# AttributeError:

# 'Son' object has no attribute 'b'

# 其他人只好委屈地修改

classSon(My,Inject):

def__init__(self):

My.__init__(self)

Inject.__init__(self)

s=Son()

print(s.b)

# 2

如果我们自己的类里使用了,那么一切都变得非常简单,其他人不管给出多少个或多少层的混合继承,都只需要一行代码即可完成初始化工作,因为将MRO链上的所有方法串在了一次,大家按照既定的顺序,不多不少都被执行了一次,硬编码问题也不复存在。

classMy:

def__init__(self):

super().__init__()

self.a=1

classInject:

def__init__(self):

super().__init__()

self.b=2

classSon(My,Inject):

def__init__(self):

super().__init__()

s=Son()

print(s.b)

# 2

友情链接: https://vonalex.github.io/

欢迎关注 它不只是Python

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180709G1U81N00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券