我们看到,程序运行后,爷爷类被初始化了两次 。
这是因为,当创建儿子对象时,会执行它的构造函数 。首先打印的是儿子类中初始化方法的代码,然后执行秦霸的构造方法 。在亲爸的构造方法中,也是先打印代码,然后执行爷爷的构造方法 。执行完爷爷的构造方法之后,程序继续执行亲爸中剩余的代码,然后回到儿子类中,执行干爹的构造方法 。在干爹的构造方法中,又要调用爷爷的构造方法 。然后打印剩余代码,直至结束 。
我们看到,第五步和第十步都是要调用爷爷的构造方法,爷爷类被初始化了两次 。这种情况一来没有必要,会占用很大空间,二来,多次初始化也会带来程序逻辑的混乱 。
如果我们改用 super 函数来进行这样的操作,就不会有这些麻烦:
class YeYe:def __init__(self):print('初始化爷爷类')class QinBa(YeYe):def __init__(self):print('进入亲爸类')super().__init__()print('初始化亲爸类')class GanDie(YeYe):def __init__(self):print('进入干爹类')super().__init__()print('初始化干爹类')class ErZi(QinBa, GanDie):def __init__(self):print('进入儿子类')super().__init__()print('初始化儿子类')erzi = ErZi()首先,我们发现,在儿子类中,我们只用一行代码指代调用两个直接父类的构造方法 。然后,从结果上看,此时,爷爷类只被初始化一次 。
而且我们发现,代码的运行情况与多个装饰器装饰一个函数的情况很类似,子类的代码包含着父类的代码,一层套一层的形式 。
查看 mro 的方法有两种:
类名.mro()对象名.__class__.mro()前面例子中的 mro 为:
[<class '__main__.ErZi'>, <class '__main__.QinBa'>, <class '__main__.GanDie'>, <class '__main__.YeYe'>, <class 'object'>]我们说过,super 后面什么都不写,默认和 super(当前类名, self) 的写法一样 。但事实上,super 的参数除了可以写当前类名外,还可以写它的父类 [^3] 的类名 。此时,会执行在方法解析顺序列表中,该类下一个类的方法 。
补充了这些知识,我们就可以解释上面的程序运行的顺序了 。
super 关键字详解:
- 基本结构:super(class[, object or class])
- Python 3 可以直接使用 super().xxx 代替 super(class, self).xxx
- 使用多继承时,会涉及到查找顺序(MRO)、钻石继承等问题单继承时,类名.__init__() 的方式和 super().__init__() 的方式调用父类中的方法没有什么差别使用 类名.__init__() 的方式在钻石继承时,会遇到初始化混乱的问题
- 我们定义的每一个类,Python 都会计算出一个方法解析顺序(MRO [^2])列表这也是 super 在父类中查找成员的顺序,它是通过 C3 线性算法来实现的
- 每个父类 [^3] 都存在且只在其中出现一次
- 我们可以通过下面两种方式获得某个类的 mro 列表:
类名.mro()对象名.__class__.mro()- 当使用 super(cls, obj) 时,Python 会在 obj 的 mro 列表上搜索 cls 的下一个类
super(cls, obj) 获得的是 cls 在 obj 的 MRO 列表中的下一个类,cls 可以是任何一个类,obj 可以是任何一个对象,只要合理即可
class class ErZi(Qinba,GanDie):def __init__(self):super(ErZi, self).__init__()print('初始化儿子')在前面我们定义儿子类的时候,如果我们不想调用亲爸的 __init__(),而是要调用干爹的 __init__(),只需把 super 写成 super(Qinba, self).__init__(),也就是这样:class YeYe:def __init__(self):print('初始化爷爷类')class QinBa(YeYe):def __init__(self):print('进入亲爸类')super().__init__()print('初始化亲爸类')class GanDie(YeYe):def __init__(self):print('进入干爹类')super().__init__()print('初始化干爹类')class ErZi(QinBa, GanDie):def __init__(self):print('进入儿子类')super(QinBa, self).__init__()print('初始化儿子类')erzi = ErZi()其执行顺序为:[^2]: Method Resolution Order
[^3]: 包括直接父类和间接父类
推荐阅读
- python中模块与包
- 跨平台Python异步聊天机器人框架,支持QQ、飞书、钉钉等渠道
- 4 步打包一个新的 Python 模块
- 什么是地震预警?手机如何实现地震预警功能?专家解读
- 汪圆圆|香港第一好命儿媳汪圆圆,模特嫁入百亿豪门,3年生3娃掌握继承权
- 何超云|真爱,父亲是千亿富豪,何超云未来可继承几十亿,却选择嫁给消防队长
- 成吉思汗到忽必烈怎么继承的?世界上成吉思汗的儿子忽必烈
- 孙策为什么把位置传给孙权?孙权是什么时候继承孙策的位置
- 康熙属意谁继承皇位?康熙让哪个儿子继位
- 编程|胡渊鸣:import一个“太极”库 让Python代码提速100倍!
