面向对象进阶

What — 是什么

Python 的面向对象系统在基础类定义之上,提供了多重继承、方法解析顺序(MRO)、混入类(Mixin)、抽象基类(ABC)等高级特性。理解这些机制是设计大型 Python 系统的关键——它们决定了代码如何复用、接口如何约束、继承如何安全组合。

核心概念:

  • 多重继承:一个类可以继承多个父类
  • MRO(Method Resolution Order):方法查找顺序,Python 使用 C3 线性化算法
  • Mixin:混入类,提供可组合的功能片段,不独立使用
  • 抽象基类(ABC):定义接口规范,子类必须实现抽象方法
  • 数据类(dataclass):自动生成 __init__/__repr__/__eq__ 等的语法糖

关键特性:

  • Python 的多重继承通过 MRO(C3 线性化)保证方法查找的确定性和一致性
  • super() 按 MRO 顺序调用下一个类的方法,不是简单的”调用父类”
  • Mixin 模式通过组合实现代码复用,避免深层继承链
  • ABC 用 @abstractmethod 强制子类实现接口,支持运行时检查
  • dataclass 大幅减少样板代码,适合纯数据容器类

运行机制:

  • MRO 计算:C3 线性化算法,保证子类在父类前、继承顺序保留、单调性
  • super() 机制super().__init__() 沿 MRO 链调用下一个类的 __init__,不是直接调用父类
  • __init_subclass__:父类在子类定义时收到通知,可自动注册/验证子类
  • __class_getitem__:支持泛型语法 MyClass[int],用于类型提示

MRO 示例:

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

D.__mro__ → [D, B, C, A, object]
# C3 线性化:D → B → C → A → object
# B 在 C 前因为 D(B, C) 声明顺序
# A 在 B/C 后因为 A 是它们的父类

Why — 为什么

适用场景:

  • 多个类需要共享通用功能(Mixin 组合)
  • 定义接口规范(ABC 抽象基类)
  • 数据容器类(dataclass)
  • 插件/策略模式的类型注册(__init_subclass__
  • 框架设计中的模板方法模式

继承 vs 组合:

维度继承组合
关系is-a(是一个)has-a(有一个)
耦合高(子类依赖父类实现)低(只依赖接口)
复用编译时确定运行时可切换
灵活性脆弱(继承链改动影响大)灵活(替换组件即可)
适用真正的 is-a 关系多数场景优先

优缺点:

  • ✅ 优点:
    • Mixin 组合比深层继承更灵活
    • MRO 保证多重继承的可预测性
    • ABC 提供接口约束,防止遗漏实现
    • dataclass 减少样板代码
  • ❌ 缺点:
    • 多重继承增加理解复杂度
    • 过度使用 Mixin 导致菱形继承和 MRO 冲突
    • super() 的行为在多重继承中不直观
    • ABC 的运行时检查有性能开销

How — 怎么用

快速上手

from abc import ABC, abstractmethod
from dataclasses import dataclass, field

# 抽象基类定义接口
class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

    def describe(self) -> str:
        return f"{self.__class__.__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}"

# dataclass 减少样板代码
@dataclass
class Circle(Shape):
    radius: float

    def area(self) -> float:
        return 3.14159 * self.radius ** 2

    def perimeter(self) -> float:
        return 2 * 3.14159 * self.radius

c = Circle(radius=5)
print(c)          # Circle(radius=5)
print(c.describe())  # Circle: area=78.54, perimeter=31.42

# Shape()  # TypeError: 无法实例化抽象类

代码示例1:Mixin 组合模式

class JsonMixin:
    """JSON 序列化/反序列化混入"""
    def to_json(self) -> str:
        import json
        return json.dumps(self.__dict__, ensure_ascii=False, default=str)

    @classmethod
    def from_json(cls, json_str: str):
        import json
        data = json.loads(json_str)
        return cls(**data)

class ValidateMixin:
    """数据验证混入"""
    def validate(self) -> list[str]:
        errors = []
        for field_name, field_type in self.__annotations__.items():
            value = getattr(self, field_name, None)
            if value is None:
                errors.append(f"{field_name} 不能为空")
            elif not isinstance(value, field_type):
                errors.append(f"{field_name} 期望 {field_type.__name__},收到 {type(value).__name__}")
        return errors

class TimestampMixin:
    """时间戳混入"""
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        import datetime
        cls._created_at = datetime.datetime.now()

# 组合多个 Mixin
@dataclass
class User(JsonMixin, ValidateMixin, TimestampMixin):
    name: str
    email: str
    age: int

user = User(name="Alice", email="alice@example.com", age=30)
print(user.to_json())
print(user.validate())  # []
print(user._created_at)  # 创建时间

# 错误数据
bad_user = User(name="Bob", email=123, age="thirty")
print(bad_user.validate())
# ['email 期望 str,收到 int', 'age 期望 int,收到 str']

代码示例2:super() 与协作式多重继承

class Base:
    def __init__(self, **kwargs):
        print("Base.__init__")
        super().__init__(**kwargs)

class A(Base):
    def __init__(self, a_val=0, **kwargs):
        self.a_val = a_val
        print(f"A.__init__(a_val={a_val})")
        super().__init__(**kwargs)

class B(Base):
    def __init__(self, b_val=0, **kwargs):
        self.b_val = b_val
        print(f"B.__init__(b_val={b_val})")
        super().__init__(**kwargs)

class C(A, B):
    def __init__(self, c_val=0, **kwargs):
        self.c_val = c_val
        print(f"C.__init__(c_val={c_val})")
        super().__init__(**kwargs)

# super() 按 MRO 顺序链式调用
# C.__mro__ → [C, A, B, Base, object]
obj = C(c_val=1, a_val=2, b_val=3)
# C.__init__(c_val=1)
# A.__init__(a_val=2)
# B.__init__(b_val=3)
# Base.__init__

print(f"a_val={obj.a_val}, b_val={obj.b_val}, c_val={obj.c_val}")
# a_val=2, b_val=3, c_val=1

# __init_subclass__:自动注册子类
class PluginRegistry:
    _plugins: dict[str, type] = {}

    def __init_subclass__(cls, name: str = "", **kwargs):
        super().__init_subclass__(**kwargs)
        plugin_name = name or cls.__name__
        PluginRegistry._plugins[plugin_name] = cls

class EmailPlugin(PluginRegistry, name="email"):
    def send(self, msg): ...

class SmsPlugin(PluginRegistry, name="sms"):
    def send(self, msg): ...

print(PluginRegistry._plugins)  # {'email': <class EmailPlugin>, 'sms': <class SmsPlugin>}

代码示例3:dataclass 高级用法

from dataclasses import dataclass, field, InitVar
from typing import ClassVar

@dataclass
class Product:
    # 基本字段
    name: str
    price: float

    # 默认值字段必须在必填字段之后
    quantity: int = 0
    tags: list[str] = field(default_factory=list)  # 可变默认值用 field

    # 类变量不计入 __init__
    tax_rate: ClassVar[float] = 0.08

    # 仅初始化变量(不存储为属性)
    discount: InitVar[float] = 0.0

    # __post_init__ 在 __init__ 后执行
    def __post_init__(self, discount: float):
        if discount > 0:
            self.price *= (1 - discount)
        if self.price < 0:
            raise ValueError("价格不能为负")

    @property
    def total(self) -> float:
        return self.price * self.quantity * (1 + self.tax_rate)

# frozen=True 不可变
@dataclass(frozen=True)
class Point:
    x: float
    y: float

    def distance_to(self, other: 'Point') -> float:
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

# 使用
p1 = Product("Laptop", 5999.0, quantity=2, discount=0.1)
print(p1)  # Product(name='Laptop', price=5399.1, quantity=2, tags=[])
print(f"总价: {p1.total:.2f}")

pt1 = Point(0, 0)
pt2 = Point(3, 4)
print(f"距离: {pt1.distance_to(pt2):.1f}")  # 5.0
# pt1.x = 5  # FrozenInstanceError

常见问题与踩坑

问题原因解决方案
MRO 冲突菱形继承的顺序不一致调整继承声明顺序,用 .__mro__ 检查
super().__init__() 参数丢失协作式继承需要 **kwargs 传递所有 __init__ 接受 **kwargs 并传递 super()
dataclass 可变默认值tags: list = [] 所有实例共享field(default_factory=list)
ABC 未实现抽象方法子类忘记实现 @abstractmethod实例化时报 TypeError,提前暴露
Mixin 与主类 __init__ 冲突Mixin 的 __init__ 被跳过Mixin 用 **kwargs 并调用 super().__init__
dataclass 继承字段顺序父类必填字段在子类必填字段前父类全部用默认值,或子类覆盖 __init__

最佳实践

  • 优先组合(composition)而非继承(inheritance)
  • Mixin 类:单一功能、无 __init__、用 **kwargs 传递 super()
  • super().__init__(**kwargs) 是协作式多重继承的关键
  • dataclass 替代手写 __init__/__repr__/__eq__
  • ABC 定义接口,@abstractmethod 强制实现
  • 继承链不超过 3 层,超过考虑组合
  • __init_subclass__ 做自动注册,替代手动注册表

面试题

Q1: Python 的 MRO 是什么?C3 线性化算法的原则是什么?

MRO(Method Resolution Order)是多重继承中方法的查找顺序。C3 线性化算法遵循三个原则:1) 子类优先于父类;2) 声明顺序保留(class D(B, C) 中 B 在 C 前);3) 单调性(如果 MRO(X) 中 A 在 B 前,则在 MRO(子类) 中 A 仍在 B 前)。可通过 ClassName.__mro__ClassName.mro() 查看。C3 保证了方法查找的确定性和一致性。

Q2: super() 在多重继承中的行为是什么?

super() 不是简单调用父类方法,而是按 MRO 顺序调用下一个类的方法。在 class C(A, B) 中,C 的 MRO 是 [C, A, B, object],C 中 super().__init__() 调用的是 A 的 __init__,A 中 super().__init__() 调用的是 B 的(不是 object 的),以此类推。这是”协作式多重继承”——每个类通过 super() 把调用传递给 MRO 中的下一个类。

Q3: Mixin 模式的最佳实践是什么?

  1. Mixin 类不独立使用,只通过多重继承混入;2) 不定义 __init__(或只用 **kwargs 传递);3) 功能单一(每个 Mixin 只提供一个能力);4) 用 super() 传递调用链;5) Mixin 放在继承列表的主类之前(class MyView(JsonMixin, LoginMixin, View));6) 避免 Mixin 之间的隐式依赖。

Q4: dataclass 和普通类有什么区别?什么时候该用?

dataclass 自动生成 __init____repr____eq____hash__(frozen 时)等方法,减少样板代码。还支持 field() 定制字段行为、__post_init__ 后处理、frozen=True 不可变。适合纯数据容器类(配置、DTO、值对象)。不适合有复杂初始化逻辑、大量方法、或需要继承非 dataclass 的场景。比 namedtuple 更强大(支持默认值、方法、继承),比 attrs 更标准库化。

Q5: @abstractmethod 的作用是什么?和 raise NotImplementedError 有什么区别?

@abstractmethod 在 ABC 中标记抽象方法,子类未实现则在实例化时抛出 TypeError(设计时发现错误)。raise NotImplementedError 是运行时抛出,只有实际调用该方法时才报错。前者更安全——在对象创建前就能保证接口完整。ABC 还支持 @abstractclassmethod@abstractproperty(已废弃,用 @property + @abstractmethod)。

Q6: __init_subclass__ 有什么用途?

__init_subclass__ 在子类定义时(不是实例化时)自动调用,用于:1) 自动注册子类到注册表(插件系统);2) 验证子类是否满足约束(必须定义某些属性/方法);3) 自动设置类属性(根据类名生成表名等)。比元类更简单,覆盖 80% 的元类使用场景。签名:def __init_subclass__(cls, **kwargs),可通过 class Sub(Base, key=value) 传递参数。

Q7: 菱形继承问题是什么?Python 如何解决?

菱形继承指 D 继承 B 和 C,B 和 C 都继承 A,D 中调用 A 的方法时不知道走 B 还是 C 的路径。Python 用 C3 线性化计算唯一的 MRO:D → B → C → A → objectsuper() 按 MRO 链式调用,A 的 __init__ 只执行一次。而 C++ 需要虚继承(virtual inheritance)解决菱形问题。Python 的方案更简洁,但要求所有 __init__ 都用 super() 协作。

Q8: ClassVar 在 dataclass 中的作用是什么?

ClassVar[T] 标注类变量,告诉 dataclass 这个字段不参与实例初始化(不加入 __init__ 参数、不计入 __repr____eq__)。适用于所有实例共享的配置(如 tax_rate: ClassVar[float] = 0.08)。与 InitVar 互补:ClassVar 是类级变量不参与初始化,InitVar 是仅用于初始化的临时变量(不存储为实例属性)。


相关链接: