Python多继承super()调用机制两个典型问题解析
问题背景
我正在深入学习Python的多继承特性以及super()函数的使用方法。
学习过程中参考了公开Python教程的示例内容,所有基础示例版权归属对应教程站点。我在教程示例基础上添加了若干print语句跟踪代码执行调用流程,完整测试代码如下:
class Rectangle: def __init__(self, length, width, **kwargs): print(f'initiated rectangle, length is {length}, width is {width}, kwargs are {kwargs}') self.length = length self.width = width super().__init__(**kwargs) def area(self): return self.length * self.width def perimeter(self): return 2 * self.length + 2 * self.width # 声明Square类继承自Rectangle类 class Square(Rectangle): def __init__(self, length, **kwargs): print(f'initiated square, length is {length}, kwargs are {kwargs}') super().__init__(length=length, width=length, **kwargs) class Cube(Square): def surface_area(self): face_area = super().area() return face_area * 6 def volume(self): face_area = super().area() return face_area * self.length class Triangle: def __init__(self, base, height, **kwargs): print(f'initiated triangle, base is {base}, height is {height}, kwargs are {kwargs}') self.base = base self.height = height super().__init__(**kwargs) def tri_area(self): return 0.5 * self.base * self.height class RightPyramid(Square, Triangle): def __init__(self, base, slant_height, **kwargs): print(f'initiated right pyramid, base is {base}, slant_height is {slant_height}, kwargs are {kwargs}') self.base = base self.slant_height = slant_height kwargs["height"] = slant_height kwargs["length"] = base print(f'initiated right pyramid finished, updated kwargs are {kwargs}') super().__init__(base=base, **kwargs) def area(self): base_area = super().area() perimeter = super().perimeter() return 0.5 * perimeter * self.slant_height + base_area def area_2(self): base_area = super().area() triangle_area = super().tri_area() return triangle_area * 4 + base_area pyramid = RightPyramid(base=2, slant_height=4)
运行上述代码,控制台输出如下:
# initiated right pyramid, base is 2, slant_height is 4, kwargs are {} -> OK # initiated right pyramid finished, updated kwargs are {'height': 4, 'length': 2} -> OK # initiated square, length is 2, kwargs are {'base': 2, 'height': 4} -> OK # initiated rectangle, length is 2, width is 2, kwargs are {'base': 2, 'height': 4} -> 疑问1 # initiated triangle, base is 2, height is 4, kwargs are {} -> 疑问2
核心疑问
Rectangle类的__init__()方法调用super()时传入了非空kwargs参数,按照常规认知此时super()指向顶层object类,为什么运行时没有抛出「object()不接收参数」的类型错误?RightPyramid类的方法解析顺序(MRO)中,Triangle类的位置比Square类更靠后,且位置靠前的Square类已经定义并执行了自身的__init__()方法,为什么Triangle.__init__()仍然会被调用?这是否是super()函数独有的特殊调用行为?
问题解答
针对第一个疑问的解答
这个认知的核心偏差是对super()的指向规则理解有误:super()的指向不是以当前类的直接父类为固定基准,而是基于当前实例所属类的MRO链、结合调用super()的类在链中的位置动态计算的。
执行print(RightPyramid.__mro__)可以直接查看该类的MRO顺序,结果为:RightPyramid -> Square -> Rectangle -> Triangle -> object
也就是说,当Rectangle的__init__中调用super()时,它在MRO链上的下一个类不是object,而是Triangle,因此传入的{'base':2, 'height':4}参数会被传给Triangle.__init__,根本不会传到object层,自然不会触发参数不匹配错误。
等Triangle.__init__执行时,它会从kwargs中取出自己需要的base和height参数赋值给实例属性,剩下的空kwargs再传给它的下一级也就是object.__init__,object本身不接收参数,空参数调用完全符合要求,不会报错。
针对第二个疑问的解答
这是Python协作式多继承的标准行为,不是特殊规则,核心逻辑还是MRO链+super()的动态跳转机制:
- 单继承场景下MRO是线性的,子类调用
super()只会走到直接父类,父类再往上走直到object,看起来像是"父类执行完就结束"。 - 多继承场景下MRO会把所有父类按C3线性化规则串成一条有序链,每个类里调用
super()都会跳转到链上的下一个类,不管这个类是不是当前类声明里写的直接父类。
本案例中,Square、Rectangle都没有中断super()调用(每个类的__init__最后都写了super().__init__(**kwargs)),所以调用链会沿着MRO一直往下走,走完Rectangle自然就到了Triangle,直到最后走到object才结束。
注意:如果某一层父类的__init__没有调用super().__init__(),整条调用链会直接中断,后面的类初始化逻辑就不会执行了,这也是写多继承代码时要求每个类都要正确传递kwargs、调用super()的核心原因。
内容的提问来源于stack exchange,提问作者karpan

