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 的计算遵循以下原则:
- 子类优先于父类
- 多个父类按照它们的声明顺序排序
- 对于每个父类,只处理一次(不会重复)
- 如果是菱形继承,基类只出现一次
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 类的特点是:
- 提供某个特定方面的功能
- 不需要复杂的初始化
- 依赖于被混入的类提供某些属性或方法
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)))
避免多重继承的陷阱
- 避免过度使用多重继承:如果可能,优先使用组合或 Mixin
- 保持继承层次扁平:避免深层继承树
- 明确依赖顺序:当必须使用多重继承时,明确基类的顺序
- 使用 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 面向对象编程中的高级主题:
- 多重继承:一个类可以继承多个父类
- 菱形继承:可能导致方法调用歧义
- MRO:Python 使用 C3 线性化算法确定方法调用顺序
- super():调用 MRO 中当前类之后的方法
- Mixin:通过多重继承添加可选功能的模式
- 组合 vs 继承:根据情况选择合适的设计方式
理解 MRO 对于正确使用多重继承至关重要。在下一节中,我们将学习多态和鸭子类型。