Python类继承中的super、MRO与C3算法详解
本文深入解析Python类继承机制,重点讲解super()在多继承中的调用规则、MRO(方法解析顺序)的生成逻辑以及C3算法的手动推导技巧。通过SGD源码及复杂继承示例,阐明初始化对属性继承的影响,并提供画图与列表推导MRO的实用规律,帮助开发者掌握Python面向对象编程的核心原理。
Python类继承机制概述
网络上关于Python中super()、MRO(方法解析顺序)及C3算法的许多介绍往往让初学者感到困惑,直接阅读C3算法的定义既困难也无必要。掌握一套实用的规律能显著降低理解门槛。本文梳理了相关技术要点,旨在提供一份易于理解的super()、MRO及C3算法的实战指南。
super()在多继承中的应用
1、super()基础
super()是连接子类与父类的关键机制。子类通过继承父类,可以复用父类的方法和属性,同时也能定义自身特有的逻辑。super()主要应用于多继承场景;在单继承中,通常直接调用父类方法即可。
以下代码节选自SGD优化器的实现,基于该源码进行分析:
class SGD(Optimizer):
def __init__(self, params, lr=required, momentum=0, dampening=0,
weight_decay=0, nesterov=False):
defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
weight_decay=weight_decay, nesterov=nesterov)
if nesterov and (momentum <= 0 or dampening != 0):
raise ValueError("Nesterov momentum requires a momentum and zero dampening")
super(SGD, self).__init__(params, defaults)
def __setstate__(self, state):
super(SGD, self).__setstate__(state)
for group in self.param_groups:
group.setdefault('nesterov', False)
这是SGD类中的核心代码片段
针对上述代码,有两点关键补充说明:
1、调用super()联系父类时,必须完整传递父类方法所需的形参。若子类参数不足,需额外补充缺失的参数。
2、super()不仅用于调用__init__构造函数,还可用于调用父类的其他任意方法。
3、在Python 3中,语法简化为super().__init__()的形式。
4、Python中所有类默认继承自object类。
此外,在super()的使用过程中,初始化行为对继承结果有重要影响:
1、若子类继承父类但未执行__init__方法,则自动继承父类的属性。
2、若子类继承父类并执行了__init__方法,但未调用super()初始化父类构造函数,则子类不会自动继承父类属性。
3、若子类继承父类并执行了__init__方法,且通过super()正确初始化了父类构造函数,则子类将成功继承父类属性。
MRO方法解析顺序详解
2、MRO(方法解析顺序)
Python的MRO(Method Resolution Order)是指调用方法时,系统对当前类及其所有基类进行搜索以确定方法所在位置的顺序。Python依据MRO确定的顺序来执行类之间的方法调用。
直接通过具体示例进行演示:
class A1():
def __init__(self):
print('A1')
super().__init__()
class A2():
def __init__(self):
print('A2')
super().__init__()
class A3():
def __init__(self):
print('A3')
super().__init__()
class B1(A1, A2):
def __init__(self):
print('B1')
super().__init__()
class B2(A2):
def __init__(self):
print('B2')
super().__init__()
class B3(A2, A3):
def __init__(self):
print('B3')
super().__init__()
class C1(B1):
def __init__(self):
print('C1')
super().__init__()
class C2(B1, B2):
def __init__(self):
print('C2')
super().__init__()
class C3(B2, B3):
def __init__(self):
print('C3')
super().__init__()
class D(C1, C2, C3):
def __init__(self):
print('D')
super().__init__()
d = D()
print(D.__mro__)
程序运行输出如下:

(<class '__main__.D'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.B1'>, <class '__main__.A1'>, <class '__main__.C3'>, <class '__main__.B2'>, <class '__main__.B3'>, <class '__main__.A2'>, <class '__main__.A3'>, <class 'object'>)
程序正是按照MRO中列出的顺序依次执行各类的方法。

如何手动推导这一顺序?这涉及到C3算法,该算法用于计算MRO并得出最终的执行顺序。
C3算法推导与实战练习
3、C3算法与规律总结
本文旨在通过C3算法揭示规律,帮助读者手动推导MRO顺序。
结合上述继承关系图,可列出如下推导表。该表源自技术社区的经验总结,被视为得出答案的有效工具。表中“实际取出的类”是如何推导出来的?以下提炼出几条直接规律:

规律1:预查找父类时遵循“左边优先”原则。例如第一行,当前类D的预查找父类选择最左侧的C1,而非C2或C3。
规律2:判断“预查找父类是否还有其他子类,且同时是最底层未查找类的父类(或父类的父类等)”。若该条件不成立,直接选取预查找父类作为结果。若条件成立,则检查“最底层的未查找父类”是否存在;若存在,优先选取它;若不存在,则选取该项类作为结果。
规律3:当预查找父类为object时,只要“最底层的未查找父类”仍存在,就选取该最底层未查找父类。若不存在,再判断“预查找父类是否还有其他子类,且同时是最底层查找类的父类(或父类的父类等)”;若有此类,则选取它;若无,则结果为object。
另外,
绘制继承图的方法如下:
1、子类始终绘制在父类下方,并使用有向箭头指向父类。
2、处理多继承时,按代码中继承列表的顺序从左往右书写。若有多个子类继承同一个父类,该父类应放置在它能出现的所有位置中最靠左的位置。需遵循广度优先遍历原则(在广度优先前提下优先遍历左侧):
练习:
以下代码来自技术社区,理解上述规律及有向图绘制方法后,即可顺利得出MRO值:
class A:
def __init__(self):
print('A')
class B(A):
def __init__(self):
print('B')
super().__init__()
class C(A):
def __init__(self):
print('C')
super().__init__()
class D(A):
def __init__(self):
print('D')
super().__init__()
class E(B, C):
def __init__(self):
print('E')
super().__init__()
class F(C, D):
def __init__(self):
print('F')
super().__init__()
class G(E, F):
def __init__(self):
print('G')
super().__init__()
首先,绘制继承图:

然后,列出推导表:
当前类最底层未查找的父类预查找的父类预查找父类是否为object预查找父类是否有其他子类,同时又是最底层查找类的父类、父父类实际取出的类GE、FEnonoEEFBnonoBBFAno有,CFFnoCnonoCCnoAno有,DDDnoAnonoAAnoobjectyesnoobject
最终答案:
mro:GEBFC
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









