Python super() 函数深度解析:从单继承到钻石继承的完整指南
本文深入解析 Python 中 super() 函数的核心机制与用法。通过单继承、多继承及钻石继承(菱形继承)的典型案例,详细对比传统类名调用与 super() 调用的差异。重点讲解 super() 如何基于 MRO(方法解析顺序)自动处理父类调用,有效避免重复初始化资源浪费,并提供 Python 2.x 与 3.x 版本间的语法区别说明。
一、super() 函数的核心用途
在深入探讨 super() 之前,明确其设计初衷至关重要。
- 该函数主要用于子类中调用父类的方法。
- 它广泛应用于多继承场景,旨在解决方法解析顺序(MRO)的查找逻辑以及钻石继承结构中的重复调用问题。
二、super() 的基础语法与机制
其标准语法格式如下:
super([type[, object-or-type]])
函数功能描述:
该函数返回一个代理对象,负责将后续的方法调用委托给 type 参数所指定类的父类或兄弟类。
参数详细说明:
type:指定类,为可选参数;object-or-type:指定对象或类,通常传入 self,也是可选参数。
返回值说明:
返回一个 super object 类型的代理对象。
官方 help 帮助信息解读:
>>> help(super)
Help on class super in module builtins:
class super(object)
| super() -> same as super(__class__, <first argument>)
| super(type) -> unbound super object
| super(type, obj) -> bound super object; requires isinstance(obj, type)
| super(type, type2) -> bound super object; requires issubclass(type2, type)
| Typical use to call a cooperative superclass method:
| class C(B):
| def meth(self, arg):
| super().meth(arg)
| This works for class methods too:
| class C(B):
| @classmethod
| def cmeth(cls, arg):
| super().cmeth(arg)
... ...
- super 本身是继承自 object 的一个类,因此调用 super() 实际上等同于实例化 super 类。
- 依据官方文档,super() 返回的 super object 本质上就是一个代理对象。
- super() 支持四种不同的参数组合形式。
- 此外,super() 同样适用于类的静态方法调用场景。
三、典型应用场景分析
3.1 单继承场景下的应用
首先分析一个最基础的子类调用父类方法的示例:
>>> class A:
def funxx(self):
print("执行 A 中的 funxx 方法 ... ...")
>>> class B(A):
def funxx(self):
A.funxx(self) # 通过类名调用父类中的同名方法,self 参数代表 B 类的实例对象 b
print("执行 B 中的 funxx 方法 ... ...")
>>> b = B()
>>> b.funxx()
执行 A 中的 funxx 方法 ... ...
执行 B 中的 funxx 方法 ... ...
- 定义一个继承自 A 类的子类 B,并在 B 类中重写 funxx() 方法。B 中的 funxx() 旨在拓展 A 中 funxx() 的功能。
- 由于需要保留 A 类 funxx() 的原有功能,因此必须在子类 B 中显式调用父类的同名方法以执行原有逻辑。
- 在上述示例中,通过 A 类类名直接调用 A 中的同名方法,此时第一个参数 self 实际传递的是 B 类的实例 b。
改用 super() 函数来实现对父类方法的调用:
>>> class A:
def funxx(self):
print("执行 A 中的 funxx 方法 ... ...")
>>> class B(A):
def funxx(self):
super().funxx()
print("执行 B 中的 funxx 方法 ... ...")
>>> b = B()
>>> b.funxx()
执行 A 中的 funxx 方法 ... ...
执行 B 中的 funxx 方法 ... ...
- 对比执行结果可知,其输出效果与传统类名调用的结果完全一致。
- 官方文档指出,在单继承的类层级结构中,使用 super 引用父类无需显式指定父类名称,从而显著提升代码的可维护性。
- 这意味着在子类中不再依赖父类名来调用父类方法,而是通过代理对象完成调用。当父类名称变更或继承关系调整时,无需逐一修改每个调用点。
3.2 单继承场景的进阶探讨
在 help() 的帮助信息中,明确指出类中使用 super() 不带参数的形式,在语义上等同于 super(__class__, <first argument>) 这种显式传参形式。这也是 Python 2.x 与 Python 3.x 在处理 super() 时的主要区别所在。
基于上述原理改写之前的单继承问题代码:
>>> class A:
def funxx(self):
print("执行 A 中的 funxx 方法 ... ...")
>>> class B(A):
def funxx(self):
super(B, self).funxx()
print("执行 B 中的 funxx 方法 ... ...")
>>> b = B()
>>> b.funxx()
执行 A 中的 funxx 方法 ... ...
执行 B 中的 funxx 方法 ... ...
- 基础调用方式
A.funxx(self),其中 self 指代实例对象 b。用自然语言描述即:实例对象 b 通过 A 类名调用方法funxx()。 - 官方描述指出:返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。用自然语言描述即:代理对象 super 通过 type 的父类或兄弟类调用其中的方法。
- 我们发现 super 是通过参数设置来选择调用哪个父类的方法。其中第二个参数提供了 MRO(方法解析顺序),即搜索目标方法的顺序,而第一个参数则限定了搜索目标方法的范围。
- 例如
super(B, self),第一个参数为 B,第二个参数 self 为实例 b,其所在类的继承顺序(MRO)为:B→A→object。因此调用时,系统会在 B 的父类 A 中寻找目标方法,若 A 中未找到,则继续向上层 object 中查找。
具体示例如下:
class A:
pass
class B(A):
pass
class C(A):
def funxx(self):
print("找到 funxx() 位于 C 中...")
class D(A):
pass
class E(B, C):
pass
class F(E, D):
def funff(self):
print("执行 F 中的 funff()...")
super(E, self).funxx()
print(f"F 类的 MRO : {F.__mro__}")
f = F()
f.funff()
运行结果如下:
F 类的 MRO : (<class '__main__.F'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
执行 F 中的 funff()...
找到 funxx() 位于 C 中...
- 从结果可以清晰看出 F 类的 MRO 顺序为:F→E→B→C→D→A→object。
- 在
super()函数中,第一个参数指定为 E,目标是调用 E 类的父类 B 中的funxx()方法。由于 B 类中未找到该方法,系统继续在 B 类的兄弟类 C 中查找并成功找到,符合预期逻辑。
3.3 重复调用问题(钻石继承)
重复调用问题也被称为钻石继承问题或菱形图问题。
先观察普通调用方法在以下场景中的表现:
>>> class A:
def __init__(self):
print("打印属性 a")
>>> class B(A):
def __init__(self):
print("打印属性 b")
A.__init__(self)
>>> class C(A):
def __init__(self):
print("打印属性 c")
A.__init__(self)
>>> class D(B, C):
def __init__(self):
print("打印属性 d")
B.__init__(self)
C.__init__(self)
>>> d = D()
打印属性 d
打印属性 b
打印属性 a
打印属性 c
打印属性 a
- 由于 B 和 C 均继承自 A,当 D 进行实例化时,A 的构造函数被执行了两次。这就是典型的重复调用问题。
- 显然,我们只需调用一次即可,重复调用只会造成不必要的资源浪费。
接下来演示如何使用 super() 函数来解决此问题:
>>> class A:
def __init__(self):
print("打印属性 a")
>>> class B(A):
def __init__(self):
print("打印属性 b")
super().__init__() # super() 等同于 super(B, self)
>>> class C(A):
def __init__(self):
print("打印属性 c")
super().__init__() # super() 等同于 super(C, self)
>>> class D(B, C):
def __init__(self):
print("打印属性 d")
super(D, self).__init__()
>>> d = D()
打印属性 d
打印属性 b
打印属性 c
打印属性 a
- 查看输出结果发现,虽然重复调用问题得以解决,但输出顺序与直觉有所差异。我们的惯性思维预期是:先执行 D 类的
__init__()方法,接着调用 B 类的__init__()方法,B 类的构造方法中又调用了父类 A 的__init_()方法,然后再调用 C 类的__init_()方法,该方法也调用了父类 A 的__init__()方法。因此预期执行顺序应为:打印属性 d,打印属性 b,打印属性 a,打印属性 c。 - 为何实际结果并非如此?首先需明确,D 类中的第二个参数 self 为 D 的实例 d,它提供的 MRO 为:D→B→C→A→object。因此 D 类中的
super()函数产生的是 d 的代理对象,当其调用父类 B 的__init__()时,B 的super()的第二个参数为 D 中的 super object,其所提供的 MRO 依旧保持为:D→B→C→A→object。这意味着 B 中的super()调用的是其上一级 C 中的__init__(),而非 A 中的__init__()。因此实际执行结果为:打印属性 d,打印属性 b,打印属性 c,打印属性 a。
3.4 super(type) 单独使用的注意事项
>>> class A:
def funxx(self):
print("...A...")
>>> class B(A):
def funxx(self):
print("...B...")
>>> sa = super(B)
>>> print(sa)
<super: <class 'B'>, NULL>
>>> print(type(sa))
<class 'super'>
可以看出,仅使用 super(type) 返回的是一个无效对象,或者称为未绑定的 super object。
至此,关于 Python super() 函数用法的总结介绍完毕。如需了解更多相关 super() 函数内容,请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













