Python3 基础教程

Python3 继承、MRO 与 super()

🎉摘要:深入理解Python继承,从单继承到多重继承,详解MRO方法解析顺序的C3线性化算法,揭示super()的真实行为——按MRO动态调用下一个类,并结合Mixin模式展示工程实践,帮你避开常见陷阱。

继承是面向对象的核心机制之一,但 Python 的继承比 Java 或 C++ 更灵活,也更复杂。它支持多重继承,并且有一套名为 C3 线性化的算法来决定方法调用顺序(MRO)。

super() 函数的行为也和直觉不太一样 —— 它并不总是调用"父类",而是按照 MRO 顺序调用"下一个类"。理解这三者的关系,是写好 Python 类层次结构的关键。

本章从基础单继承讲起,逐步深入到多重继承、MRO 和 super() 的协作机制。

继承基础

单继承与方法重写

单继承指一个子类只继承一个父类,语法:

class 父类:
    pass

class 子类(父类):
    pass

子类自动拥有父类全部属性、普通方法,复用父类代码,是 Python 最基础的继承形式。

简单示例:

class Animal:
    """动物类的基类"""
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        return f"{self.name} makes a sound"

    def move(self) -> str:
        return f"{self.name} is moving"

class Dog(Animal):
    """狗类,继承自动物类"""
    def __init__(self, name: str, breed: str):
        super().__init__(name)  # 调用父类构造
        self.breed = breed

    def speak(self) -> str:
        return f"{self.name} says: Woof!"

# 使用
d = Dog("Buddy", "Golden Retriever")
print(d.speak())   # Buddy says: Woof!
print(d.move())    # Buddy is moving  —— 继承自 Animal

上面代码,Dog 继承了 Animal 的 move 方法,但重写了 speak 方法。super().__init__(name) 调用了父类的构造函数,避免重复初始化 name 字段。这是 super() 在单继承中最常见的用法,看起来简单,但它已经为后面的复杂性埋下了伏笔。

继承的查找规则

当你访问 d.speak() 时,Python 的查找顺序是:

  1. 实例 d 的 __dict__;

  2. 类 Dog 的 __dict__;

  3. 按照 MRO 顺序继续查找父类。

如果没有找到,抛出 AttributeError。这个查找顺序不是简单的"父类→祖类",而是 MRO 决定的线性序列。

多重继承

Python 从设计之初就支持多重继承:一个类可以同时继承多个父类。语法:

class 父类1:
    pass

class 父类2:
    pass

class 子类(父类1, 父类2):
    pass

简单示例:

class Flyer:
    """飞行类"""
    def fly(self) -> str:
        return f"{self.name} is flying"

class Swimmer:
    """游泳类"""
    def swim(self) -> str:
        return f"{self.name} is swimming"

class Duck(Flyer, Swimmer):
    """鸭子类,继承了飞行类和游泳类"""
    def __init__(self, name: str):
        self.name = name

d = Duck("Donald")
print(d.fly())   # Donald is flying
print(d.swim())  # Donald is swimming

多重继承的好处是代码复用极其灵活:Duck 同时获得了飞行和游泳的能力。但问题也来了:如果 Flyer 和 Swimmer 都定义了同名的方法(比如 move()),Python 该调用哪个?

菱形继承问题(Diamond Problem)

什么是菱形继承,简单来说就是继承结构像菱形:

        父类A
      /     \
    B类     C类
      \     /
        D子类

上图中,D 同时继承 B、C,B 和 C 又都继承了同一个父类 A,形成菱形结构。

如果 A 里有构造方法、成员属性、同名方法,不加特殊处理时,父类 A 会被重复初始化、方法重复执行。这就是多重继承中最经典的陷阱。

简单示例:

class A:
    def greet(self):
        print("A says hello")

class B(A):
    def greet(self):
        print("B says hello")
        super().greet() # 注意:使用的是 super()

class C(A):
    def greet(self):
        print("C says hello")
        super().greet()

class D(B, C):
    pass

d = D()
d.greet()

运行代码,输出:

B says hello
C says hello
A says hello

上面代码分别先调用 B 类的 greet()、C 类的 greet() 和 A 类的 greet(),后续介绍为什么会这样调用。

MRO:方法解析顺序

查看 MRO

Python3 使用 C3 线性化算法(C3 Linearization)计算 MRO。每个类都有一个 __mro__ 属性,展示方法查找的线性顺序:

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

print(D.__mro__)
# 输出:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

# 等价方法
print(D.mro())
# 输出:[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

MRO 的排列规则:

  1. 子类永远在父类前面;

  2. 如果有多个父类,按继承声明的顺序从左到右;

  3. 共同祖先(如 A)出现在所有子类之后。

对于 D(B, C),MRO 是 D → B → C → A → object。当调用 d.greet() 时,Python 沿着这个顺序找第一个匹配的 greet() 方法。

MRO 的构造直觉

C3 线性化算法看起来很玄,但可以通过一个简单的直觉理解:

D(B, C) 的 MRO = D + merge(MRO(B), MRO(C), [B, C])

即:把子类自己放在最前面,然后递归合并父类的 MRO 列表。合并时保持父类内部的相对顺序,同时保证子类在父类前面。如果无法构造一个满足所有约束的线性序列,Python 会抛出 TypeError: Cannot create a consistent method resolution order。

# 这个会报错
class X(A, B): pass  # 如果 A 和 B 的顺序违反约束

MRO 在 Python 中的实际应用

下面通过例子展示私有 MRO 和不使用 MRO 的区别:

class Base:
    def __init__(self):
        print("Base init")
        self.base_flag = True

class Mixin1(Base):
    def __init__(self):
        print("Mixin1 init")
        super().__init__() # 使用 MRO
        self.mixin1_flag = True

class Mixin2(Base):
    def __init__(self):
        print("Mixin2 init")
        super().__init__() # 使用 MRO
        self.mixin2_flag = True

class Combined(Mixin1, Mixin2):
    def __init__(self):
        print("Combined init")
        super().__init__() # 使用 MRO
        self.combined_flag = True

print("MRO:", [c.__name__ for c in Combined.__mro__])
c = Combined()
print("Flags:", hasattr(c, 'base_flag'), hasattr(c, 'mixin1_flag'), hasattr(c, 'mixin2_flag'))

运行代码,输出:

MRO: ['Combined', 'Mixin1', 'Mixin2', 'Base', 'object']
Combined init
Mixin1 init
Mixin2 init
Base init
Flags: True True True

代码 super().__init__() 中的 super() 不绑定固定类,依托当前实例的 MRO 线性继承列表动态取下一个类,是动态顺着 MRO 串行调用,天然适配多重继承、菱形继承。

注意:所有 __init__ 都被调用了一次,包括 Base!这就是 super() 配合 MRO 的威力。如果 Mixin1 直接写 Base.__init__(self),结果就会完全不同。例如:

class Base:
    def __init__(self):
        print("Base init")
        self.base_flag = True

class Mixin1(Base):
    def __init__(self):
        print("Mixin1 init")
        Base.__init__(self) # 不使用 MRO,直接调用 Base 类的初始方法
        self.mixin1_flag = True

class Mixin2(Base):
    def __init__(self):
        print("Mixin2 init")
        Base.__init__(self) # 不使用 MRO
        self.mixin2_flag = True

class Combined(Mixin1, Mixin2):
    def __init__(self):
        print("Combined init")
        super().__init__()
        self.combined_flag = True

print("MRO:", [c.__name__ for c in Combined.__mro__])
c = Combined()
print("Flags:", hasattr(c, 'base_flag'), hasattr(c, 'mixin1_flag'), hasattr(c, 'mixin2_flag'))

运行代码,输出:

MRO: ['Combined', 'Mixin1', 'Mixin2', 'Base', 'object']
Combined init
Mixin1 init
Base init
Flags: True True False

上面显式指定类名调用(Base.__init__(self)),完全无视 MRO 继承链路,就是单纯以当前实例为入参,强行执行指定类的构造函数,是静态绑定。

执行路径:

  1. Combined.__init__()

    • 打印 Combined init

    • super().__init__() 依照 MRO 走到 Mixin1.__init__()

  1. Mixin1.__init__()

    • 打印 Mixin1 init

    • 手写 Base.__init__(self):直接调用 Base 构造,打印 Base init

    • 绑定 mixin1_flag

  1. Mixin1 构造执行完毕,回到 Combined 的 super 调用结束,不会自动进入 Mixin2;

super():不是调用父类,而是调用 MRO 的下一个类

这是 Python 继承中最容易误解的点。super() 不是"调用父类",而是 "调用 MRO 中当前类的下一个类"。

单继承中的 super()

示例代码:

class Animal:
    def __init__(self, name):
        self.name = name
        print(f"Animal init: {name}")

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 等价于 super(Dog, self).__init__(name)
        self.breed = breed
        print(f"Dog init: {breed}")

d = Dog("Buddy", "Golden")
# Animal init: Buddy
# Dog init: Golden

在单继承中,super() 确实调用了父类,因为 MRO 中 Dog 的下一个类就是 Animal。这让人误以为 super() = "父类",但多重继承会打破这个直觉。

多重继承中的 super() 真相

示例代码:

class A:
    def __init__(self):
        print("A")
        super().__init__()

class B(A):
    def __init__(self):
        print("B")
        super().__init__()

class C(A):
    def __init__(self):
        print("C")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D")
        super().__init__()

print("MRO:", [c.__name__ for c in D.__mro__])
d = D()

输出:

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

super() 的调用链是:

  • D.__init__ 里的 super() → 调用 B.__init__;

  • B.__init__ 里的 super() → 调用 C.__init__(不是 A!);

  • C.__init__ 里的 super() → 调用 A.__init__;

  • A.__init__ 里的 super() → 调用 object.__init__(空操作)。

注意:B 的 super() 没有调用 B 的父类 A,而是调用了 MRO 中 B 的下一个类 C。这就是 super() 的真正语义:按照 MRO 的线性序列,调用下一个类的方法。

为什么这样设计?

这种设计解决了菱形问题中共同祖先被重复调用的问题。如果 B 和 C 都用 super(),那么 A 只会被调用一次,因为 B 的 super() 把控制权交给了 C,C 的 super() 再交给 A。整个调用链沿着 MRO 走一遍,每个类只执行一次。

class A:
    def __init__(self):
        print("A")
        super().__init__()

# 对比:不用 super() 用直接调用
class B_direct(A):
    def __init__(self):
        print("B")
        A.__init__(self)  # 硬编码调用 A

class C_direct(A):
    def __init__(self):
        print("C")
        A.__init__(self)  # 硬编码调用 A

class D_direct(B_direct, C_direct):
    def __init__(self):
        print("D")
        B_direct.__init__(self)
        # 如果这里也调 C_direct.__init__(self),A 会被调用两次

print("MRO:", [c.__name__ for c in D_direct.__mro__])
d = D_direct()
# 输出:D → B → A(C 被跳过了!)
# 或者 D → B → A → C → A(A 被调两次!)

调用代码,输出:

MRO: ['D_direct', 'B_direct', 'C_direct', 'A', 'object']
D
B
A

不用 super() 的话,你不得不在 D 里手动决定调用 B 还是 C,或者两个都调导致 A 重复。super() + MRO 的组合让这个决策自动化了。

super() 的完整用法与参数

Python3 的零参数 super()

class Child(Parent):
    def method(self):
        super().method()  # Python3 推荐写法

Python3 的 super() 可以不带参数,编译器会自动推断出当前类和当前实例。这是语法糖,等价于 super(Child, self),但更可读、更不容易出错。

Python2 风格:显式传参(兼容代码中可见)

class Child(Parent):
    def method(self):
        super(Child, self).method()  # Python 2 风格

两个参数的含义:

  • 第一个参数:type,当前类;

  • 第二个参数:object 或 type,当前实例或子类。

super(Child, self) 会从 MRO 中 Child 的下一个类开始查找。如果你传入的是子类(比如 super(Child, GrandChild)),它会从 MRO 中 Child 的下一个类开始查找,跳过 Child 本身。这在复杂元编程中偶尔用到,但日常代码里没必要。

使用 super() 调用特定方法

class Base:
    def work(self, x):
        return x * 2

class Child(Base):
    def work(self, x):
        base_result = super().work(x)
        return base_result + 1

c = Child()
print(c.work(5))  # 11  (5*2 + 1)

super().work(x) 调用了 MRO 中下一个类的 work 方法。在单继承中就是父类,但记住这个语义在多重继承中同样适用。

Mixin 模式:多重继承的工程实践

Mixin 是一种特殊的多重继承用法:用小型、单一的类提供某种功能,本身不单独实例化。配合 super(),Mixin 可以像积木一样组合。例如:

class BaseHolder:
    def __init__(self, *args, **kwargs):
        # 吃掉所有多余关键字参数,不再往上抛给 object
        pass
    
class JsonSerializable:
    """Mixin:提供 JSON 序列化能力"""
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class Timestamped:
    """Mixin:提供创建时间戳"""
    def __init__(self, *args, **kwargs):
        from datetime import datetime
        self.created_at = datetime.now().isoformat()
        super().__init__(*args, **kwargs)

class Validated:
    """Mixin:参数校验"""
    def __init__(self, *args, **kwargs):
        if 'name' not in kwargs or not kwargs['name']:
            raise ValueError("name is required")
        super().__init__(*args, **kwargs)

class User(Timestamped, Validated, JsonSerializable, BaseHolder):
    """用户类,继承多个 Mixin,组装出一个功能丰富的类"""
    def __init__(self, name: str, age: int = 0):
        super().__init__(name=name, age=age)  # 按 MRO 链式传递 kwargs
        self.name = name
        self.age = age

if __name__ == "__main__":
    u = User("Alice", 30)
    print(u.to_json())
    # 输出类似:{"name": "Alice", "age": 30, "created_at": "2024-..."}

这个例子有几个关键设计点:

  1. 每个 Mixin 的 __init__ 都接受 *args, **kwargs 并传给 super(),保证参数能沿 MRO 链传递;

  2. Timestamped 把 created_at 注入实例,不依赖具体业务类;

  3. 3. User 的继承顺序决定了初始化顺序:User → Timestamped → Validated → JsonSerializable → BaseHolder → object。

注意:Mixin 的继承顺序很重要。有依赖的 Mixin(如 Validated 需要提前检查参数)应该放在后面(靠近基类),因为 super() 是从左到右执行的。

常见陷阱

忘记在所有类里调用 super()

class A:
    def __init__(self):
        print("A")
        super().__init__()

class B(A):
    def __init__(self):
        print("B")
        # 忘记 super()!

class C(B):
    def __init__(self):
        print("C")
        super().__init__()

C()
# 输出:C → B(A 被跳过了!)

只要链上有一个类没调 super(),MRO 链就断了。设计 Mixin 或基类时,如果预期会被组合,每个 __init__ 都必须调用 super()。

MRO 不可构造的情况

class X: pass
class Y(X): pass
class Z(X): pass

# 下面的声明违反了约束:Y 在 X 前面,但 Z 继承要求 X 在 Z 前面
class A(Y, Z): pass
# 可以,MRO 没问题

class B(Z, Y): pass
# 也可以

class C(A, B): pass  # TypeError!

错误信息如下:

Traceback (most recent call last):
  File "d:\python_demo\demo.py", line 12, in <module>
    class C(A, B): pass  # TypeError!
    ^^^^^^^^^^^^^^^^^^^
TypeError: Cannot create a consistent method resolution order (MRO) for bases Y, Z

C(A, B) 会报错,因为 A 的 MRO 要求 Y 在 Z 前面,而 B 的 MRO 要求 Z 在 Y 前面,无法构造一个同时满足两者的线性序列。遇到这种错误,说明类层次设计有冲突,需要重构继承关系。

super() 在类方法中的使用

class Base:
    @classmethod
    def create(cls, name):
        print(f"Base.create: {cls.__name__}")
        return cls(name)

class Child(Base):
    def __init__(self, name):
        print(f"Child.__init__: {name}")
        self.name = name
        
    @classmethod
    def create(cls, name):
        print(f"Child.create: {cls.__name__}")
        return super().create(name)

c = Child.create("test")
print(type(c))  # <class '__main__.Child'>
print(c.name)  # test

运行代码,输出如下:

Child.create: Child
Base.create: Child
Child.__init__: test
<class '__main__.Child'>
test

super() 在类方法里同样遵循 MRO,返回的绑定对象会把方法调用转发到 MRO 中的下一个类。cls 参数会被正确传递,这是工厂模式配合继承的标准写法。

检查对象类型

class Animal: pass
class Dog(Animal): pass

d = Dog()
print(isinstance(d, Dog))      # True
print(isinstance(d, Animal))   # True
print(issubclass(Dog, Animal)) # True
print(issubclass(Dog, object)) # True

isinstance 和 issubclass 都会考虑 MRO。isinstance(d, Animal) 为 True,是因为 Animal 在 Dog 的 MRO 中。isinstance 比 type(d) == Dog 更灵活,通常优先使用。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号