Python super() 函数详解:用法、原理与多重继承最佳实践
本文详细解析 Python 中 super() 函数的使用方法与底层原理。通过实例展示如何在子类 __init__ 中正确调用父类,覆盖特殊方法,以及解决多重继承导致的重复初始化问题。深入讲解方法解析顺序 (MRO) 和 C3 线性化算法,帮助开发者理解 super() 的工作机制,编写更健壮的面向对象代码。
super() 功能概述
功能
super() 函数的主要功能是允许子类调用父类(或超类)中定义的方法。
super() 的主要用法
用法
1. 在子类的 __init__() 方法中正确初始化父类,确保相同的基类仅被初始化一次。
2. 用于覆盖(重写)特殊方法。
3. 解决多重继承场景中,子类重复调用父类方法的问题。
使用注意事项:新式类与经典类
注意
super() 仅适用于新式类(New-style classes)。若用于经典类(Classic classes),将会引发错误。
新式类的定义:必须继承自某个类;如果没有显式继承,则默认继承 object 类。
经典类的定义:没有父类。此时调用 super() 会报错:'super() argument 1 must be type, not classobj'。
正确初始化父类
在子类 __init__() 方法中正确初始化父类,保证相同的基类只初始化一次
假设父类中已实现某个方法,子类希望在使用该方法的基础上进行扩展,而不希望完全重写。特别是在多继承场景下,super() 能够实现方法的增量修改。
例如,A(父类)拥有 x 属性,B(子类)希望添加 y 属性:
父类与子类属性初始化示例
class A(object):
def __init__(self,x):
self.x = x
class B(A):
def __init__(self,x,y):
super(B,self,).__init__(x)
self.y = y
a = A(2)
b = B(2,4)
print(a.x)
print(b.x,b.y)
覆盖特殊方法
覆盖 Python 特殊方法
代理对象中的 super() 应用
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
在上述代码中,__setattr__() 的实现包含名称检查逻辑。如果属性名以下划线 (_) 开头,则通过 super() 调用原始的 __setattr__();否则,将处理委派给内部代理对象 self._obj。值得注意的是,即使没有显式指明某个类的父类,super() 依然能有效工作。
解决多重继承中的重复调用问题
解决多重继承中,子类重复调用父类方法的问题
未使用 super() 的重复初始化示例
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
虽然对于简单代码这通常不是问题,但在涉及复杂多继承的代码中,这可能导致奇怪的行为。例如考虑以下情况:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__')
class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')
运行这段代码会发现 Base.__init__() 被调用了两次,结果如下:
>>> c = C()
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
>>>
虽然两次调用 Base.__init__() 可能没有坏处,但在某些情况下并非如此。另一方面,如果代码中改用 super(),结果就会非常完美:
使用 super() 优化后的初始化流程
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
运行这个新版本后,你会发现每个 __init__() 方法仅被调用一次:
>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>
深入理解:方法解析顺序 (MRO)
为了弄清其原理,我们需要花时间解释 Python 是如何实现继承的。对于你定义的每一个类,Python 会计算出一个所谓的方法解析顺序 (MRO) 列表。这个 MRO 列表就是一个简单的所有基类的线性顺序表。例如:
查看类的 MRO 列表
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>
为了实现继承,Python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配该属性的类为止。
而 MRO 列表的构造是通过一个 C3 线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
简单来说,你所需要知道的是 MRO 列表中的类顺序会让你定义的任意类层级关系变得有意义。
当你使用 super() 函数时,Python 会在 MRO 列表上继续搜索下一个类。只要每个重定义的方法统一使用 super() 并只调用它一次,那么控制流最终会遍历完整个 MRO 列表,每个方法也只会被调用一次。这也是为什么在第二个例子中你不会调用两次 Base.__init__() 的原因。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













