继承=子类自动获得父类的属性与方法,再按需扩展(加新)或重写(改旧)。多父类(多重继承)能力更强但坑更多——Python 用 MRO 裁决冲突。经验法则:继承表达【是一种】;组合表达【有一个】;拿不准时组合优先。
一、继承:复用的正道
Animal(父类)
属性 name / 方法 speak
/ \
Dog(子类) Bird(子类)
重写 speak 重写 speak + 新方法 fly
子类得到父类的全部家底,然后:
- 扩展:加父类没有的(Bird.fly);
- 重写(override):用自己的实现盖掉父类的(speak 各叫各的)。
继承是【是一种】(is-a)关系:狗是一种动物,所以 Dog 继承 Animal 合理。
二、多重继承与 MRO
Python 允许一个类继承多个父类(C++ 也支持;Java 单继承+接口)。多继承的必答题:两个父类都有同名方法,听谁的?Python 的答案叫 MRO(方法解析顺序):按 C3 线性化算法排定查找顺序——先写在前的父类优先,且保证每个类只访问一次。查看:类名.__mro__。
教材同时警告:菱形继承(D 继承 B、C,B、C 都继承 A)是多继承经典陷阱——重复继承、构造顺序混乱,C++ 用虚继承、Python 用 MRO 化解。能不用多继承就不用:用组合或接口替代。
三、组合优于继承
【汽车有引擎】不该写成 Car 继承 Engine(汽车是一种引擎?错!)——Car 里放一个 Engine 成员(has-a)。组合的灵活性:换引擎不改车身;继承则是出生就焊死。这句【composition over inheritance】是面向对象最重要的经验法则之一。
四、动手 Demo:继承、重写、MRO 三连
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Bird(Animal):
def speak(self): # 重写
return "啾"
def fly(self): # 扩展
return f"{self.name} 会飞"
class Fish(Animal):
def swim(self):
return f"{self.name} 在游"
class FlyingFish(Bird, Fish): # 多重继承:飞鱼 = 鸟 + 鱼
pass
f = FlyingFish("尼莫")
print(f.speak()) # MRO 先找到 Bird 的 speak
print(f.fly(), "/", f.swim()) # 两边的扩展都能用
print([c.__name__ for c in FlyingFish.__mro__])
# 重写后再调父类版本:super()
class LoudDog(Animal):
def speak(self):
return super().speak().upper() + "!汪!"
print(LoudDog("旺财").speak())
沙箱实跑输出:
啾
尼莫 会飞 / 尼莫 在游
['FlyingFish', 'Bird', 'Fish', 'Animal', 'object']
...!汪!
看到什么算成功:FlyingFish 会飞也会游;MRO 顺序打印清楚;LoudDog 在父类实现基础上再加工。
读结果:speak() 命中 Bird(MRO 里 Bird 在 Fish 前面);super() 不是【调父类】而是【沿 MRO 找下一个】——单继承时两者等价,多继承时 super 才显出真功力。重写不是推翻父类,常常是复用+增强。
五、30 秒实验
把 class FlyingFish(Bird, Fish) 的顺序改成 (Fish, Bird) 重跑——f.speak() 依然是【啾】(沙箱实测):Fish 自己的类字典里没有 speak,MRO 顺序 [飞鱼2, Fish, Bird, Animal] 接着查 Bird 就命中了。想让结果改变,得让 Fish 自己重写 speak——MRO 查的是整条链上先命中者,继承顺序即行为,多继承慎用。
六、常见误区
| 误区 | 真相 |
|---|---|
| 有代码可复用就继承 | 复用不是继承的理由;【是一种】才是;否则用组合 |
| 多继承多多益善 | 菱形结构、冲突裁决都是坑;能用组合或接口就不用 |
| 重写后父类实现就没了 | super() 随时可回父类逻辑——重写常是【包装】而非【替换】 |
自测三问(点击展开)
1. **is-a 和 has-a 各对应什么?** 答:is-a(是一种)用继承;has-a(有一个)用组合——拿不准时组合优先。 2. **Python 多继承的同名冲突怎么裁决?** 答:MRO(C3 线性化):写在前的父类优先,`__mro__` 可查。 3. **菱形继承的麻烦是什么?** 答:同一祖先被访问两次、构造顺序混乱——C++ 虚继承/Python MRO 化解。- ☐ 5-16 学习完成
相关笔记
- C++ 11.1 · 继承 —— 继承的 C++ 语法面
- C++ 11.3 · 多重继承 —— 菱形与虚继承的深水区
- 多态与接口 —— 重写的高光时刻
- ⌨️ 编程原理 · 索引 —— 全课路线图