继承与多态
本教程共 70 篇 · 第 33 篇 · 更新于 2026-07-22 · 约 6 分钟阅读
33. 继承与多态
本节目标:学会用
class Child(Parent)继承父类,理解方法重写和super()的用法,感受多态带来的代码灵活性。
写代码时经常遇到这样的场景:两个类长得很像,只是细节不同。比如 Dog 和 Cat,都会叫、都会跑,但叫声不一样。
复制粘贴一份代码改改也能用,但维护时要在两处同步修改,很痛苦。继承(Inheritance)就是来解决这个问题的。
继承的基本语法
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} 发出声音")
class Dog(Animal):
pass
d = Dog('旺财')
print(d.name) # 旺财
Dog(Animal) 表示 Dog 继承自 Animal。Dog 没有写 __init__,但创建实例时自动调用了父类的方法,name 属性就有了。
Tip所有类默认继承自
object。写class Animal:和class Animal(object):效果一样。在 Python 3 里,推荐省略(object)。
方法重写(Override)
子类对父类的方法不满意,可以重新写一个同名的。这叫「重写」。
class Dog(Animal):
def speak(self):
print(f"{self.name}: 汪汪汪!")
class Cat(Animal):
def speak(self):
print(f"{self.name}: 喵喵喵~")
Dog('旺财').speak() # 旺财: 汪汪汪!
Cat('咪咪').speak() # 咪咪: 喵喵喵~
调用的是哪个 speak,取决于实例的真实类型,不是变量名。
super():调用父类的方法
重写时,子类经常想在父类逻辑的基础上加点东西,而不是完全替换。super() 帮你调用父类:
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的 __init__
self.breed = breed # 再加自己的逻辑
def speak(self):
super().speak() # 先执行父类的 speak
print(f"({self.name} 摇着尾巴)")
d = Dog('旺财', '金毛')
d.speak()
# 输出:
# 旺财 发出声音
# (旺财 摇着尾巴)
super().__init__(name) 等价于 Animal.__init__(self, name),但更好:如果父类改了名字,子类代码不用动。
NotePython 3 里
super()可以不带参数。Python 2 必须写super(Dog, self),忘掉它吧。
多态(Polymorphism)
多态的字面意思是「多种形态」。在 Python 里,它体现为:同一个函数,传入不同类型的对象,表现不同。
def animal_concert(animal):
animal.speak()
animal_concert(Dog('旺财'))
animal_concert(Cat('咪咪'))
animal_concert 不在乎传入的是 Dog 还是 Cat,只要对象有 speak() 方法就行。这就是「鸭子类型(duck typing)」:看起来像鸭子、叫起来像鸭子,那它就是鸭子。
再举个例子,内置函数 len() 也是多态的:
print(len('hello')) # 字符串,长度 5
print(len([1, 2, 3])) # 列表,长度 3
print(len({'a': 1})) # 字典,长度 1
len() 不关心对象的具体类型,只关心对象有没有 __len__ 方法。
判断类型:type vs isinstance
有时候你需要判断一个对象是不是某个类的实例。用 isinstance:
d = Dog('旺财')
print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True(Dog 是 Animal 的子类)
print(type(d) == Dog) # True
print(type(d) == Animal) # False
isinstance 会考虑继承链,type 只返回最精确的类型。判断类型时,优先用 isinstance。
Warning过度使用
isinstance会破坏多态的灵活性。如果你写了很多if isinstance(x, Dog): ... elif isinstance(x, Cat): ...,往往说明设计可以优化。
继承的坑:菱形继承
如果继承结构复杂,可能出现「菱形继承」:
A
/ \
B C
\ /
D
D 同时继承 B 和 C,而 B、C 都继承 A。这时 D 里调用 super() 会走哪条路?Python 用 C3 线性化算法决定了方法解析顺序(MRO)。
class A:
def hello(self):
print('A')
class B(A):
def hello(self):
print('B')
class C(A):
def hello(self):
print('C')
class D(B, C):
pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D().hello() # B
__mro__ 属性显示了方法查找顺序。D 先找自己,再找 B,然后 C,然后 A。B 排在 C 前面,因为 class D(B, C) 里 B 写在左边。
Note多重继承后面会专门讲(第 36 章)。这里只需要知道:Python 有明确的 MRO 规则,不会乱。
实战:设计一个简单的员工系统
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def get_bonus(self):
return self.salary * 0.1
def info(self):
print(f"{self.name}: 工资 {self.salary}, 奖金 {self.get_bonus():.0f}")
class Developer(Employee):
def get_bonus(self):
return self.salary * 0.15
class Manager(Employee):
def get_bonus(self):
base = super().get_bonus()
return base + 5000
staff = [Employee('小张', 8000), Developer('小李', 10000), Manager('老王', 15000)]
for s in staff:
s.info()
Employee 定义了通用逻辑。Developer 重写奖金比例,Manager 在父类奖金基础上再加固定金额。info() 里调用 get_bonus() 时,自动走到各自子类的实现。这就是多态的威力。
小结
- 继承让子类复用父类的代码,减少重复。
- 方法重写让子类定制自己的行为。
super()安全地调用父类方法,方便扩展。- 多态让代码更灵活,同一套逻辑适配不同对象。
继承不是「为了用而用」。如果两个类确实符合「is-a」关系(狗是一种动物),才适合继承。如果只是有些功能相同,组合(composition)往往是更好的选择。
来源:参考了 runoob「Python3 面向对象」、liaoxuefeng「继承和多态」、pythondoc「9. 类」等,改写后所得。