python学习笔记6.4-类的多重继承(super()函数)

说到面向对象,就少不了研究面向对象的特点(继承,封装,多态)。Python中类的继承的关键是正确使用super()函数,而这恰好是我们理解最不好的地方。先看看一般类的继承的代码(关于我写的类的详解1就是这么写,现在觉得写法实在比较粗糙):

class Base:
    def __init__(self):
        print('This is Base init function')
class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('This is init function of A')

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print('This is init function of B')

class C(A,B):
    def __init__(self):
        #super().__init__(self)
        A.__init__(self)
        B.__init__(self)
        print('This is init function of C')

c = C()

控制台输出:
This is Base init function
This is init function of A
This is Base init function
This is init function of B
This is init function of C

从打印输出来看,初始化确实也没有什么问题。只是Base()被初始化两次,并不影响其功能,只是程序员接受不了。接下来用super()函数重写该段代码:

class Base:
    def __init__(self):
        print('This is Base init function')
class A(Base):
    def __init__(self):
        super().__init__()
        print('This is init function of A')

class B(Base):
    def __init__(self):
        super().__init__()
        print('This is init function of B')

class C(A,B):
    def __init__(self):
        super().__init__()
        #A.__init__(self)
        #B.__init__(self)
        print('This is init function of C')

c = C()
打印输出:
This is Base init function
This is init function of B
This is init function of A
This is init function of C

用super()函数重写之后,Base的初始化函数就只运行了一次。 要理解为什么会这样,我们得先去理解python是如何实现类的继承的。针对于每一个定义的类,python都会计算出一个方法解析顺序(MRO)的列表。MRO列表只是简单地对所有的基类进行线性排列:

print(C.__mro__)
print(type(C.__mro__))

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
<class 'tuple'>

可以看出,MRO是以元组的结构来储存的,同时,要实现继承,python从MRO最左边的类开始(子类),从左到右依次查找,直到查找到待查的属性为止。 MRO列表是如何确定的呢,python采用的是C3线性化处理(C3 linearization)的技术。简单来说就是一种针对父类的归并排序它满足3个条件:

(1)先检查子类,再检查父类 (2)有多个父类(多重继承),按照MRO列表的顺序依次检查 (3)如果下一个类中出现两个合法的选择,那么就从第一个父类中选择(避免重复继承,保证每个父类只继承一次)

当使用super()函数时,python会继续从MRO中的下一个类开始搜索,只要每一个重新定义过的方法(比如init())都使用了super()函数,并且调用了他们一次,那么控制流最终就可以遍历整个MRO列表,并且让每个方法都只被调用一次(这就是第二个例子中为什么Base.init()只被调用一次的原因)。 关于super()函数,还有一个很神奇的功能:它并不是一定要关联到某个类的直接父类上,甚至可以在没有直接父类的类上使用它。例如:

class A:
    def __init__(self):
        print('This is init function of A')

class B:
    def __init__(self):
        print('This is init function of B')
        super().__init__()

class C(B,A):
    pass

c = C()

打印输出:
This is init function of B
This is init function of A

那么问题来了,A并不是B的父类,但是B中使用super函数仍然可以调用A中的init()。这个就可以利用C的MRO列表来解释:

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

B中使用了super()它就会遍历MRO,寻找下一个方法,在A中找到了,所以就调用了它。 这就说明,在使用super()函数时可能会产生我们并不想要的结果,所以我们要遵守一些基本规则来避免这些情况的发生。例如:确保在继承体系中所有同名的方法都有可兼容的调用签名(参数数量相同,参数名称也相同,则只会调用一个)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

Python错误及异常总结汇总

程序员的一生中,错误几乎每天都在发生。在过去的一个时期, 错误要么对程序(可能还有机器)是致命的,要么产生一大堆无意义的输出,无法被其他计算机或程序识别,连程...

35011
来自专栏向治洪

react-native 之布局总结

前言 之前我们讲了很多react-native的基础控件,为了方便大家的理解,我们来对react-native的布局做一个总结,观看本节知识,你将看到。 宽度单...

3838
来自专栏前端桃园

JavaScript核心概念之执行上下文和栈

现在想改变一下写作方式,以问答的形式来讲解这些枯燥无味的知识,尽量把每一个为什么都讲透,每个知识点都不迷惑。

461
来自专栏Python疯子

TebsorFlow基本语法

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invi...

632
来自专栏Android机动车

转向Kotlin——数据类和封闭类

数据类是Kotlin的一个语法糖。Kotlin编译器会自动为数据类生成一些成员函数,以提高开发效率。

662
来自专栏阮一峰的网络日志

jQuery最佳实践

上周,我整理了《jQuery设计思想》。 那篇文章是一篇入门教程,从设计思想的角度,讲解"怎么使用jQuery"。今天的文章则是更进一步,讲解"如何用好jQue...

3486
来自专栏海天一树

小朋友学C++(20):内联函数

第(2)种方法比第(1)种方法,有三个优点: ① 阅读和理解函数 max 的调用,要比读一条等价的条件表达式并解释它的含义要容易得多 ② 如果需要做任何修改,修...

622
来自专栏Web项目聚集地

CSS布局解决方案(居中布局)

前端布局非常重要的一环就是页面框架的搭建,也是最基础的一环。在页面框架的搭建之中,又有居中布局、多列布局以及全局布局,今天我们就来总结总结前端干货中的CSS布局...

822
来自专栏老马寒门IT

01-老马jQuery教程-jQuery入口函数及选择器

这套jQuery教程是老马专门为寒门子弟而录制,希望大家看到后能转发给更多的寒门子弟。视频都是免费,请参考课程地址:https://chuanke.baidu....

1990
来自专栏进击的君君的前端之路

设计模式

1052

扫码关注云+社区