Day 26 - 多重继承与方法解析顺序(MRO)

多重继承的概念

多重继承(Multiple Inheritance)是指一个类可以直接继承多个父类。Python 完整地支持多重继承,这使得类可以从多个不相关的类中继承功能。然而,多重继承也带来了复杂性,特别是在方法解析顺序(Method Resolution Order,MRO)和菱形继承问题上。

class Flyer:
    def fly(self):
        print("我可以飞翔")

class Swimmer:
    def swim(self):
        print("我可以游泳")

class Walker:
    def walk(self):
        print("我可以行走")

class Duck(Flyer, Swimmer, Walker):
    """鸭子继承三种能力"""
    pass

duck = Duck()
duck.fly()   # 我可以飞翔
duck.swim()  # 我可以游泳
duck.walk()  # 我可以行走

菱形继承问题

菱形继承(Diamond Inheritance)是指两个类继承自同一个基类,然后又有一个类同时继承这两个类,形成菱形结构。这种结构在多重继承中容易引发问题:如果基类有实例属性或方法,在子类中应该如何访问?

class A:
    def greet(self):
        print("A 的 greet 方法")

class B(A):
    def greet(self):
        print("B 的 greet 方法")
        super().greet()

class C(A):
    def greet(self):
        print("C 的 greet 方法")
        super().greet()

class D(B, C):
    def greet(self):
        print("D 的 greet 方法")
        super().greet()

# 创建 D 的实例并调用 greet
d = D()
d.greet()

输出:

D 的 greet 方法 B 的 greet 方法 C 的 greet 方法 A 的 greet 方法

方法解析顺序(MRO)

Python 使用 C3 线性化算法来计算方法解析顺序。MRO 是一个确定性算法,确保在多重继承中方法的调用顺序是明确且一致的。

每个类都有一个 __mro__ 属性,是一个元组,包含类的方法解析顺序(不包括类自己)。

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# 查看 MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

# 使用 mro() 方法
print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

super() 在多重继承中的行为

super() 的行为与 MRO 密切相关。super() 不是简单调用"直接父类"的方法,而是调用 MRO 中当前类"之后"的下一个类。

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

class Left(Base):
    def __init__(self):
        print("Left.__init__")
        super().__init__()

class Right(Base):
    def __init__(self):
        print("Right.__init__")
        super().__init__()

class Child(Left, Right):
    def __init__(self):
        print("Child.__init__")
        super().__init__()

print("创建 Child 对象:")
child = Child()
print("\nChild 的 MRO:")
for cls in Child.__mro__:
    print(f"  {cls.__name__}")

输出:

创建 Child 对象: Child.__init__ Left.__init__ Right.__init__ Base.__init__ Child 的 MRO: Child Left Right Base object

深入理解 MRO

MRO 的计算遵循以下原则:

  1. 子类优先于父类
  2. 多个父类按照它们的声明顺序排序
  3. 对于每个父类,只处理一次(不会重复)
  4. 如果是菱形继承,基类只出现一次
class X:
    pass

class Y:
    pass

class A(X, Y):
    pass

class B(Y, X):  # 注意顺序与 A 相反
    pass

class C(A, B):
    pass

print("C 的 MRO:")
for cls in C.__mro__:
    print(f"  {cls.__name__}")

Mixin 类的设计

Mixin 是一种设计模式,用于在多重继承中添加可选功能。Mixin 类的特点是:

  1. 提供某个特定方面的功能
  2. 不需要复杂的初始化
  3. 依赖于被混入的类提供某些属性或方法
class DistanceMixin:
    """距离计算混入"""
    def distance_to(self, other):
        """计算到另一个点的距离(假设其他对象有 x, y 属性)"""
        dx = self.x - other.x
        dy = self.y - other.y
        return (dx**2 + dy**2)**0.5

class ColorMixin:
    """颜色混入"""
    def set_color(self, color):
        self.color = color
    
    def get_color(self):
        return getattr(self, 'color', '未设置')

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __repr__(self):
        return f"Point({self.x}, {self.y})"

class ColoredPoint(Point, DistanceMixin, ColorMixin):
    """带颜色的点,同时具有距离计算功能"""
    pass

p1 = ColoredPoint(0, 0)
p2 = ColoredPoint(3, 4)

p1.set_color("红色")
print(f"p1 颜色:{p1.get_color()}")  # 红色
print(f"p1 到 p2 的距离:{p1.distance_to(p2):.2f}")  # 5.00

组合 vs 继承

在设计类层次结构时,我们应该优先考虑"组合(Composition)“还是"继承(Inheritance)"?这是一个重要的设计决策。

继承的优点

  • 代码复用
  • 自然的"是一个"关系
  • 多态支持

继承的缺点

  • 类层次耦合度高
  • 不够灵活
  • 可能导致菱形继承问题

组合的优点

  • 低耦合
  • 更灵活
  • 容易测试

组合的缺点

  • 需要更多的代码来实现代理方法
# 继承方式
class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)

# 组合方式
class Dog:
    def __init__(self, name):
        self.animal = Animal(name)  # 组合

钻石继承的完整示例

class BaseGeometry:
    """基础几何类"""
    def __init__(self):
        print("BaseGeometry.__init__")
        super().__init__()

class Polygon(BaseGeometry):
    """多边形类"""
    def __init__(self, sides):
        print(f"Polygon.__init__ (sides={sides})")
        self.sides = sides
        super().__init__()
    
    def area(self):
        raise NotImplementedError("子类必须实现 area 方法")

class Quadrilateral(Polygon):
    """四边形类"""
    def __init__(self, length, width):
        print(f"Quadrilateral.__init__ ({length}x{width})")
        self.length = length
        self.width = width
        super().__init__(4)  # 四边形有4条边
    
    def area(self):
        return self.length * self.width

class Rectangle(Quadrilateral):
    """矩形类"""
    def __init__(self, length, width):
        print(f"Rectangle.__init__ ({length}x{width})")
        super().__init__(length, width)
    
    def perimeter(self):
        return 2 * (self.length + self.width)

class Square(Rectangle):
    """正方形类"""
    def __init__(self, side):
        print(f"Square.__init__ (side={side})")
        super().__init__(side, side)

# 测试菱形继承
print("创建正方形:")
sq = Square(5)
print(f"边数:{sq.sides}")  # 4
print(f"面积:{sq.area()}")  # 25
print(f"周长:{sq.perimeter()}")  # 20

方法解析顺序的高级用法

可以使用 super(ClassName, self) 的形式来调用 MRO 中特定类之后的方法。

class A:
    def method(self):
        print("A.method")
        return "A"

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

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

class D(B, C):
    def method(self):
        print("D.method")
        # 调用 B 之后的方法(也就是 C)
        return super(B, self).method()

d = D()
result = d.method()
print(f"返回值:{result}")
# D.method
# C.method
# A.method
# 返回值:A

super 的等价形式

在 Python 3 中,super() 等价于 super(ClassName, self),其中 ClassName 是定义 super() 调用的类的名称。

class Base:
    def greet(self):
        return "Base"

class Left(Base):
    def greet(self):
        return f"Left({super().greet()})"

class Right(Base):
    def greet(self):
        return f"Right({super().greet()})"

class Child(Left, Right):
    def greet(self):
        return f"Child({super().greet()})"

# 测试
child = Child()
print(child.greet())
# Child(Left(Right(Base)))

避免多重继承的陷阱

  1. 避免过度使用多重继承:如果可能,优先使用组合或 Mixin
  2. 保持继承层次扁平:避免深层继承树
  3. 明确依赖顺序:当必须使用多重继承时,明确基类的顺序
  4. 使用 Mixin:将可选功能提取为 Mixin 类
# 不推荐:深层继承
class Level1:
    pass

class Level2(Level1):
    pass

class Level3(Level2):
    pass

class Level4(Level3):
    pass

# 推荐:扁平结构 + Mixin
class FeatureMixin:
    pass

class Base:
    pass

class Child(FeatureMixin, Base):
    pass

练习题

练习 1:计算 MRO

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

class E(C, B):
    pass

class F(D, E):
    pass

print("各类的 MRO:")
print(f"D: {[c.__name__ for c in D.__mro__]}")
print(f"E: {[c.__name__ for c in E.__mro__]}")
print(f"F: {[c.__name__ for c in F.__mro__]}")

练习 2:实现可排序的类

class ComparableMixin:
    """可比较混入"""
    def _compare_to(self, other):
        raise NotImplementedError
    
    def __eq__(self, other):
        if other is None:
            return False
        return self._compare_to(other) == 0
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __lt__(self, other):
        if other is None:
            return False
        return self._compare_to(other) < 0
    
    def __le__(self, other):
        return self == other or self < other
    
    def __gt__(self, other):
        return other is not None and self._compare_to(other) > 0
    
    def __ge__(self, other):
        return self == other or self > other

class Person(ComparableMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def _compare_to(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.age - other.age
    
    def __repr__(self):
        return f"Person({self.name}, {self.age})"

# 测试
p1 = Person("张三", 25)
p2 = Person("李四", 30)
p3 = Person("王五", 25)

print(p1 < p2)   # True
print(p1 == p3)  # False(不同对象)
print(p1 != p2)  # True
print(p1 < p3)   # False(年龄相同)

练习 3:实现日志功能的 Mixin

import logging
from datetime import datetime

class LogMixin:
    """日志混入:给类添加日志功能"""
    
    def __init__(self):
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.setLevel(logging.DEBUG)
        if not self.logger.handlers:
            handler = logging.StreamHandler()
            handler.setFormatter(logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            ))
            self.logger.addHandler(handler)
    
    def log_info(self, message):
        self.logger.info(message)
    
    def log_debug(self, message):
        self.logger.debug(message)
    
    def log_error(self, message):
        self.logger.error(message)

class BankAccount(LogMixin):
    def __init__(self, account_id, balance=0):
        super().__init__()
        self.account_id = account_id
        self.balance = balance
        self.log_info(f"创建账户 {account_id},初始余额 {balance}")
    
    def deposit(self, amount):
        if amount <= 0:
            self.log_error(f"存款失败:金额 {amount} 无效")
            return False
        self.balance += amount
        self.log_info(f"存款 {amount},新余额 {self.balance}")
        return True
    
    def withdraw(self, amount):
        if amount > self.balance:
            self.log_error(f"取款失败:余额不足(余额 {self.balance},取款 {amount})")
            return False
        self.balance -= amount
        self.log_info(f"取款 {amount},新余额 {self.balance}")
        return True

# 测试
account = BankAccount("ACC001", 1000)
account.deposit(500)
account.withdraw(200)
account.withdraw(2000)  # 会记录错误日志

总结

多重继承和方法解析顺序(MRO)是 Python 面向对象编程中的高级主题:

  1. 多重继承:一个类可以继承多个父类
  2. 菱形继承:可能导致方法调用歧义
  3. MRO:Python 使用 C3 线性化算法确定方法调用顺序
  4. super():调用 MRO 中当前类之后的方法
  5. Mixin:通过多重继承添加可选功能的模式
  6. 组合 vs 继承:根据情况选择合适的设计方式

理解 MRO 对于正确使用多重继承至关重要。在下一节中,我们将学习多态和鸭子类型。