Python中super()函数的原理、MRO机制与常见陷阱解析

Leo
Leo
发布时间:2026-10-15 | 5人浏览过
原创

本文深入解析Python中super()函数的核心机制,指出其并非直接调用父类,而是依据MRO(方法解析顺序)列表查找并调用下一个类。文章通过菱形继承案例演示C3线性化算法,并重点剖析在多重继承中混用super与显式调用导致的重复执行问题,以及参数签名不一致引发的TypeError。最后提供使用*args和**kwargs的解决方案及注意事项,帮助开发者避免常见陷阱。

前言

Python作为一门面向对象编程语言,类的继承是核心特性之一。子类可以继承父类已封装的方法,无需重新编写。若子类重定义父类方法,则会覆盖父类同名方法。但在某些场景下,我们需要在保留父类功能的基础上进行扩展,而非完全覆盖,此时便需先调用父类方法,再执行子类特有逻辑。Python提供了super()函数来实现这一需求,确保父类方法被正确调用。

super的用法

看下面一个例子:


class A:
    def func(self):
        print("A的func执行")


class B(A):

    def func(self):
        super().func()
        print("B扩展的func执行")


b = B()
b.func()
# 输出结果为:
# A的func执行
# B扩展的func执行

在上述代码中,A为父类,B为A的子类。我们在A类中定义了func()方法,在B类中也定义了func()方法。在B类的func()实现中,通过super().func()显式调用了父类A的方法。因此,执行结果会依次输出A类func()的内容以及B类扩展的功能。

在查阅Python内置库及第三方库源码时,常会发现super()被广泛用于子类中调用父类的__init__()初始化方法。这种用法在面向对象设计中极为常见,用于确保父类资源被正确初始化。


class A:
    def __init__(self, x):
        self.x = x

class B(A):

    def __init__(self, x, y):
        super().__init__(x)
        self.y = y
    

b = B(1, 2)
print(b.x, b.y)

许多人直观认为super()的作用是获取父类实例并调用其方法,这种理解并不准确。实际上,super()返回的是当前类在MRO(Method Resolution Order,方法解析顺序)列表中的下一个类。MRO列表决定了多继承时的方法查找顺序。我们可以通过以下几种方式获取类的MRO列表:


C.mro()
C.__mro__
c.__class__.__mro__

MRO列表的确定算法历经多次演变,目前Python采用C3线性化算法。该算法的核心逻辑是将所有父类的MRO列表合并,并遵循以下三条基本原则:

  • 子类永远在父类前面
  • 如果有多个父类,会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

下面来看一下下面这个例子:


class A(Base):
    def func(self):
        print("A的func执行")
        super().func()
        print("A的func执行完毕")


class B(Base):
    def func(self):
        print("B的func执行")
        super().func()
        print("B的func执行完毕")

class C(A, B):
    def func(self):
        print("C的func执行")
        super().func()
        print("C的func执行完毕")


c = C()
c.func()
# 获取MRO列表
print(c.__class__.__mro__)

执行结果如下:

上述程序中,Base是父类,A、B都继承自Base,C继承自 A、B,它们的继承关系就是一个典型的菱形继承,如下:

通过结果我们可以看出,super并不是获取父类并用来调用父类的方法,而是根据MRO列表一次调用下一个类,使用c.__class__.__mro__可以获取MRO列表,MRO列表的顺序是C、A、B、Base、object。

super的原理

super计算方法解析顺序中的下一个类,可以接收两个参数:


def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]
  • 通过inst负责生成MRO列表
  • 通过cls定位在MRO列表中的index, 并返回mro[index + 1]

Python super()使用注意事项

Python 中,由于基类不会在 __init__() 中被隐式地调用,需要程序员显式调用它们。这种情况下,当程序中包含多重继承的类层次结构时,使用 super 是非常危险的,往往会在类的初始化过程中出现问题。

混用super与显式类调用

分析如下程序,C 类使用了 __init__() 方法调用它的基类,会造成 B 类被调用了 2 次:


class A:
    def __init__(self):
        print("A",end=" ")
        super().__init__()
class B:
    def __init__(self):
        print("B",end=" ")
        super().__init__()
class C(A,B):
    def __init__(self):
        print("C",end=" ")
        A.__init__(self)
        B.__init__(self)
print("MRO:",[x.__name__ for x in C.__mro__])
C()

运行结果为:

MRO: ['C', 'A', 'B', 'object'] C A B B

出现以上这种情况的原因在于,C 的实例调用 A.__init__(self),使得 super(A,self).__init__() 调用了 B.__init__() 方法。换句话说,super 应该被用到整个类的层次结构中。

但是,有时这种层次结构的一部分位于第三方代码中,我们无法确定外部包的这些代码中是否使用 super(),因此,当需要对某个第三方类进行子类化时,最好查看其内部代码以及 MRO 中其他类的内部代码。

不同种类的参数

使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名,一个类怎么能调用其基类的 __init__() 代码呢?这会导致下列问题:


class commonBase:
    def __init__(self):
        print("commonBase")
        super().__init__()
class base1(commonBase):
    def __init__(self):
        print("base1")
        super().__init__()
class base2(commonBase):
    def __init__(self):
        print("base2")
        super().__init__()
class myClass(base1,base2):
    def __init__(self,arg):
        print("my base")
        super().__init__(arg)
myClass(10)

运行结果为:

my base Traceback (most recent call last): File "C:\Users\mengma\Desktop\demo.py", line 20, in <module> myClass(10) File "C:\Users\mengma\Desktop\demo.py", line 19, in __init__ super().__init__(arg) TypeError: __init__() takes 1 positional argument but 2 were given

一种解决方法是使用 *args 和 **kwargs 包装的参数和关键字参数,这样即使不使用它们,所有的构造函数也会传递所有参数,如下所示:


class commonBase:
    def __init__(self,*args,**kwargs):
        print("commonBase")
        super().__init__()
class base1(commonBase):
    def __init__(self,*args,**kwargs):
        print("base1")
        super().__init__(*args,**kwargs)
class base2(commonBase):
    def __init__(self,*args,**kwargs):
        print("base2")
        super().__init__(*args,**kwargs)
class myClass(base1,base2):
    def __init__(self,arg):
        print("my base")
        super().__init__(arg)
myClass(10)

运行结果为:

my base base1 base2 commonBase

不过,这是一种很糟糕的解决方法,由于任何参数都可以传入,所有构造函数都可以接受任何类型的参数,这会导致代码变得脆弱。另一种解决方法是在 MyClass 中显式地使用特定类的 __init__() 调用,但这无疑会导致第一种错误。

总结

现在我们知道:supper获取的是MRO列表中的下一个类,当前类的父类没有实质性的关系;还有如何查看MRO列表。最后需要注意的是super以及MRO列表,针对都是Python新式类!

英语好的话可以读一下这边文章Python's super() considered super

到此这篇关于python中super()函数的理解与基本使用的文章就介绍到这了,更多相关python中super()函数内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表