python面向对象编程系列文章(一)——python的多继承与单继承(MRO解析)

前言:很久没有系统的更新python高级语法与高级特性相关的东西了,本次带来的更新是关于python面向对象的设计的一些注意事项,包括以下几个内容:python的多继承与单继承、python中得super类的本质以及应用、python中的__init__和__new__详解三个内容,本篇为系列文章第一篇,详解python多继承与单继承。

 

一、python的继承

面向对象程序设计有两类最基本的关系:

  • 一类是父与子的继承关系,即所谓的继承关系,在Python中描述为 issubclass(type1,type2);
  • 另一类是所谓的实例与类的关系,在Python中描述为 isinstance(obj,type)。

在很多面向对象的程序设计语言中,继承都是单继承,像C#,Java等,单继承有很好的优点;也有部分语言支持多继承,像C++,python同时支持单继承与多继承。

(1)单继承:

就是一个类只能从一个父类中继承,如C继承自父类B,B继承自父类A,这是单继承;

(2)多继承:

就是一个类同时继承自多个父类,如,C同时继承自B和A。

 

1.1 什么是方法解析顺序 MRO

Python的MRO即Method Resolution Order(方法解析顺序),也就是在Python中的类的继承顺序是怎样的。在Python2.3之前,MRO的实现是基于DFS(深度优先搜索)的,而在Python2.3以后MRO的实现是基于C3算法(我这里两种算法的具体实现都不详述)。

C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。

  • 本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
  • 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。

(1)类的 __mro__属性

python中每一个类都有一个属性 __mro__ ,这个属性会返回该类中方法的搜索顺序,这个属性返回的是一个元组tuple,每一个元素都是一个类,如下例子:

class Base:
    pass

class Parent(Base):
    pass

class Son(Parent):
    pass

print(Parent.__mro__)
print(Son.__mro__)
'''
(<class '__main__.Parent'>, <class '__main__.Base'>, <class 'object'>)
(<class '__main__.Son'>, <class '__main__.Parent'>, <class '__main__.Base'>, <class 'object'>)
'''

什么意思呢?就是如果我现在创建一个Son类的实例s,通过 s.xxxx()调用某一个方法,会在上面的顺序中进行查找,最先是在Son类本身查找有没有这个方法,依次向上查找。

总结:这其实就是一个单继承了,它不会造成函数的重复调用,这里就不再多说了。

1.2 python 单继承

class Base:
    def func(self):
        print("i am Base")

class Parent(Base):
    def func(self):
        print("i am Parent")

class Son(Parent):
    def func(self):
        print("i am Son")

s=Son()
s.func()  # 打印 i am Son

单继承一般不会造成

1.3 多继承——非钻石继承

所谓非钻石继承是相对于钻石继承而言的,钻石继承会在下面说明。指的是一个子类同时继承自多个父类(基类/超类),但是每一个父类继承自不同的父类。

class Base1:
    pass

class Base2:
    pass

class Father(Base1):
    pass

class Mother(Base2):
    pass

class Son(Father,Mother):
    pass

print(Son.__mro__)
'''
(<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Base1'>, <class '__main__.Mother'>, <class '__main__.Base2'>, <class 'object'>)
'''

上面便是方法在类中的搜索顺序。

1.4 多重继承——钻石继承

如果子类继承自两个单独的超类,而那两个超类又继承自同一个公共基类,那么就构成了钻石继承体系。这种继承体系很像竖立的菱形,也称作菱形继承。如下所示:

class Base1:
    pass

class Father(Base1):  # 两个父类又都继承自公众的基类
    pass

class Mother(Base1):
    pass

class Son(Father,Mother):
    pass

print(Son.__mro__)
'''
(<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class '__main__.Base1'>, <class 'object'>)
'''

1.5 多重交叉继承

如果有更加复杂的继承关系,如下所示:

如下代码所示:

class D:
    pass
class E:
    pass
class F:
    pass
class B(D,E):
    pass
class C(D,F):
    pass
class A(B,C):
    pass

print(A.__mro__)
'''
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)
'''

总结:python新式类的多重继承遵循的算法是C3算法,它在处理多重继承关系的时候有以下几个原则:

  1. 子类永远在父类前面
  2. 如果有多个父类,会根据它们在列表中的顺序被检查
  3. 如果对下一个类存在两个合法的选择,选择第一个父类(每一个类只会出现一次,不会重复搜索)

 二、python多重继承的问题

从上面的python多重继承原则中我们可以知道,当调用子类对象的一个方法的时候,搜索路径是确定的,按照子类的 __mro__属性所返回的元组的顺序依次查找这个方法名称,那为什么还会有一些缺点呢?

什么时候会出现错误呢?在多重继承的时候,我们需要在子类中调用父类函数的时候容易出现错误。

2.1 一个简单的例子

我们看下面的例子来说明一下:

class Base:
    def func(self):
        print('i am Base')

class Father(Base):
    def func(self):
        Base.func(self)
        print("i am Father")

class Mother(Base):
    def func(self):
        Base.func(self)
        print("i am Mother")

class Son(Father,Mother):
    def func(self):
        Father.func(self) 
        Mother.func(self)
        print("i am Son")

s=Son()
s.func()
'''
i am Base
i am Father
i am Base
i am Mother
i am Son
'''

整个的执行顺序是这样子的:

第一步:调用 s.func() ,然后进入到 Father.func(),然后进入到 Base.func(),打印出

i am Base
i am Father

第二步:执行完Father.func()之后 ,然后进入到 Mother.func(),然后进入到 Base.func(),打印出
i am Base
i am Mother

第二步:最后打印出
i am Son

从运行的逻辑上来说完全没有问题,但是有一问题就是,我的Father和Mother都是调用了基类Base.func(),而基类Base.func()中的内容相当于是重复执行了一次,这也就是为什么会打印出两个i am Base的原因了,我们只期望Base只执行一次,及我们的目的是如果能够自动检测到父类Father中已经执行了基类的func函数,那么Mother就不再重复执行这一次了,这样就更加合理了,怎么做呢?这就是通过我们的super类完成。

注意:上面的执行逻辑以及打印出来的结果不是错误,程序是完全没有问题的,是完全正确的,只不过我们不期望Base同样的内容不执行两次而已。

2.2 解决上面问题的方法——通过super类来执行父类方法

上面之所以会出现这样的问题,主要的地方在于下面这个地方:

class Son(Father,Mother):
    def func(self):
        Father.func(self)  # 由于Son有两个父类,即多重继承,在这里显示的调用了每一个父类的方法
        Mother.func(self)
        print("i am Son")

更改过之后如下:

class Base:
    def func(self):
        print('i am Base')

class Father(Base):
    def func(self):
        super().func()
        print("i am Father")

class Mother(Base):
    def func(self):
        super().func()
        print("i am Mother")

class Son(Father,Mother):
    def func(self):
        super().func()   # 这个地方改成一句话了,不再显示调用两个父类的方法,而是让解释器根据MRO规则自己判断
        print("i am Son")

s=Son()
s.func()
'''
i am Base
i am Mother
i am Father
i am Son
'''

现在的过程是这样的,

首先进入Son中的func函数,然后跳转到第一个父类Father的func函数,然后再跳转到第二个父类Mother的func函数,再跳转到基类Base的func函数,所以打印出这样的结果。我们发现它的搜索顺序是

Son——>Father——>Mother——>Base

这不就是前面的 1.4 中的那个顺序吗?而且在多重继承的时候,不再显示的一个一个调用多个父类函数,而是通过super类完成,解释器会自动进行搜索,保证基类中同样的函数只执行一次。

关于super类的详细使用,将在系列文章的下一篇深入说明。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值