继承
约 1549 字大约 5 分钟
2026-01-22
继承(Inheritance) 是面向对象编程(OOP)的三大特性之一(另外两个是封装和多态)。它允许我们创建一个新的类(子类/派生类),从已有的类(父类/基类)那里“继承”属性和方法。
- 基本概念
- 单继承
- 多继承
- 继承 vs 组合 (Composition)
- 1写一个 Animal 父类和 Dog/Cat 子类,重写 speak(),再用一个 def make_sound(a): a.speak() 统一调用。
- 2在子类 __init__ 里用 super().__init__(...) 调父类初始化 —— 漏写会得到“没初始化的属性”。
- 3搭一个 A → B、A → C、(B,C) → D 的菱形继承,看 D.mro() 的顺序,并跑 d.process() 看 super() 走 MRO 链。
- 4对“UserService 继承 FileLogger 来用 log 方法”这种类型 —— 改成组合(self.logger = FileLogger())会更通顺。
- 5记住:is-a 用继承,has-a 用组合;超过两层继承就该检讨是不是过度抽象。
基本概念
继承(Inheritance) 是面向对象编程(OOP)的三大特性之一(另外两个是封装和多态)。它允许我们创建一个新的类(子类/派生类),从已有的类(父类/基类)那里“继承”属性和方法。
为什么需要继承?
- 代码复用:子类可以直接使用父类的代码,避免重复造轮子。
- 扩展性:子类可以在保留父类功能的基础上,增加新的功能或修改原有行为。
- 逻辑结构:建立类之间的层级关系(Is-A 关系),例如:“狗”是一种“动物”。
单继承
最简单的继承形式是一个子类继承一个父类。
基本语法
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f'{self.name} makes a sound.')
# Dog 继承自 Animal
class Dog(Animal):
def speak(self):
print(f'{self.name} says Woof!')
class Cat(Animal):
pass
dog = Dog('Buddy')
dog.speak() # 输出: Buddy says Woof! (重写了父类方法)
cat = Cat('Kitty')
cat.speak() # 输出: Kitty makes a sound. (使用了父类方法)方法重写(Overriding)
如上例所示,子类可以定义与父类同名的方法。这被称为方法重写。调用该方法时,优先执行子类的版本。这是实现多态的基础。
super() 函数
在子类中,如果想调用父类被重写的方法(通常是 __init__),可以使用 super() 函数。
class Bird(Animal):
def __init__(self, name, can_fly):
# 调用父类的初始化方法,初始化 name
super().__init__(name)
self.can_fly = can_fly
def speak(self):
# 也可以在普通方法中调用父类方法
super().speak()
print(f'I can fly: {self.can_fly}')
bird = Bird('Polly', True)
bird.speak()
# 输出:
# Polly makes a sound.
# I can fly: True提示
使用 super() 可以避免硬编码父类名称,并且在多继承场景下能正确处理方法的调用顺序。
多继承
Python 支持多继承,即一个类可以同时继承多个父类。
class Flyer:
def fly(self):
print('I can fly')
class Swimmer:
def swim(self):
print('I can swim')
class Duck(Flyer, Swimmer):
pass
donald = Duck()
donald.fly() # I can fly
donald.swim() # I can swim菱形继承(Diamond Inheritance)
当多个父类继承自同一个基类,而子类又同时继承这些父类时,就形成了菱形继承。这会带来“方法的二义性”问题:如果不小心,基类的方法可能会被调用多次。
Python 通过 MRO(Method Resolution Order,方法解析顺序) 完美解决了这个问题。
MRO (Method Resolution Order)
Python 使用 C3 线性化算法 来确定方法的调用顺序。确保:
- 子类优先于父类被检查。
- 多个父类根据它们在列表中的顺序被检查。
- 如果两个父类继承自同一个基类,基类最后被检查。
你可以通过 ClassName.mro() 或 ClassName.__mro__ 查看类的解析顺序。
class A:
def process(self):
print('A process')
class B(A):
def process(self):
print('B process')
super().process()
class C(A):
def process(self):
print('C process')
super().process()
class D(B, C):
def process(self):
print('D process')
super().process()
# 查看 MRO
print(D.mro())
# Output: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# 调用顺序
d = D()
d.process()输出分析:
D process
B process
C process
A process注意到 A 的 process 只被调用了一次!这就是 super() 配合 MRO 的威力。它不是简单地调用“父类”,而是调用 MRO 链条中的“下一个类”。
继承 vs 组合 (Composition)
在面向对象设计中,有一个著名的原则:多用组合,少用继承 (Favor composition over inheritance)。
- 继承 (Inheritance):体现 "Is-A" 关系(如:Dog is an Animal)。关系是静态的,编译时确定的。
- 组合 (Composition):体现 "Has-A" 关系(如:Car has an Engine)。关系是动态的,更灵活。
什么时候用组合?
当你想利用另一个类的代码,但又不想继承它的接口(所有方法)时,或者两个类之间没有明显的层级关系时,应该使用组合。
class Engine:
def start(self):
print('Engine started')
class Car:
def __init__(self):
self.engine = Engine() # 组合:Car 拥有一个 Engine
def start(self):
print('Car starting...')
self.engine.start()
my_car = Car()
my_car.start()继承的缺点
- 强耦合:子类高度依赖父类的实现。如果父类修改了代码,可能会导致所有子类出错(Fragile Base Class 问题)。
- 层级爆炸:过度使用继承会导致继承链过深,难以理解和维护。
总结
- 继承 是实现代码复用和多态的重要手段。
- 使用
super()来调用父类方法,它能正确处理多继承中的 MRO。 - 多继承 功能强大但容易增加复杂性,需理解 MRO 机制。
- 在设计系统时,优先考虑 组合,仅在确实存在 "Is-A" 关系且需要多态时使用继承。
- 继承表达 is-a 关系,用来复用结构 + 实现多态;用 class Sub(Parent): 一行声明。
- 子类调用父类方法用 super(),比硬编码 ParentName.method(self) 更适配多继承。
- 多继承靠 C3 算法做 MRO;菱形继承下 super() 沿 MRO 链走,基类只被调一次。
- 工程上多用组合(has-a)少用继承:耦合更低,替换更容易,继承链过深就该重构。
- 继承不是“蹭代码”的工具 —— 父类一改子类全部受影响,Fragile Base Class 是常见陷阱。
版权所有
版权归属:Shuo Liu
