Python 继承和多态:站在巨人的肩膀上编程
引言:家族基因的传承
想象一个家族:爷爷会种地,爸爸继承了种地的技能,又学会了开拖拉机;儿子继承了爷爷和爸爸的所有技能,还学会了无人机喷洒农药。
每一代人都不用从零开始——上一代的技能直接"遗传"下来,新一代只需学习新增的技能,或者改进旧技能(比如用无人机代替人工)。
Python 的**继承(Inheritance)**就是编程世界的"基因传承":子类自动获得父类的全部能力,还能新增自己的特色,或者改写父类不适合的做法。
而**多态(Polymorphism)**则是:不管家族里哪一代人,只要叫他"干活",他都会用自己的方式去完成——爷爷用锄头,爸爸用拖拉机,儿子用无人机。
一、继承:子类自动获得父类的能力
1.1 定义父类
class Animal(object):
def run(self):
print('Animal is running...')Animal 是父类(也叫基类、超类),它有一个 run() 方法。
1.2 定义子类:括号里写父类名
class Dog(Animal):
pass
class Cat(Animal):
passDog 和 Cat 什么都没写,只是括号里声明了"我继承 Animal"。但它们已经自动拥有了 run() 方法:
dog = Dog()
dog.run() # Animal is running...
cat = Cat()
cat.run() # Animal is running...生活化理解:爸爸会开拖拉机,儿子不用重新学,天生就会——这就是继承。
1.3 子类新增自己的方法
class Dog(Animal):
def eat(self):
print('Eating meat...')现在 Dog 既有继承来的 run(),又有自己特有的 eat():
dog = Dog()
dog.run() # Animal is running...(继承来的)
dog.eat() # Eating meat...(自己特有的)继承的核心价值:代码复用。不用重复写公共逻辑,子类只需关注"我和父类有什么不同"。
二、方法重写:子类改写父类的做法
2.1 问题:所有动物都跑成一个样
上面的 Dog 和 Cat 调用 run(),输出的都是 Animal is running...——不够精确。狗跑和猫跑应该不一样。
2.2 子类覆盖父类方法
在子类中重新定义同名方法,就会覆盖父类的版本:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')现在:
dog = Dog()
dog.run() # Dog is running...
cat = Cat()
cat.run() # Cat is running...规则:调用方法时,Python 先找子类,找不到再找父类,还找不到就报错。
生活化理解:爸爸用拖拉机耕地,儿子说"我不用拖拉机,我用无人机"——他重写了爸爸的方法。但儿子仍然"会耕地"这个技能,只是方式变了。
三、多态:同一接口,不同实现
3.1 先理解:子类实例也是父类类型
dog = Dog()
>>> isinstance(dog, Dog)
True
>>> isinstance(dog, Animal)
Truedog 既是 Dog 类型,也是 Animal 类型——因为 Dog 是 Animal 的一种。
但反过来不行:
animal = Animal()
>>> isinstance(animal, Dog)
FalseAnimal 不是 Dog——"动物"不一定是"狗"。
生活化理解:儿子是家族成员,但家族成员不一定是儿子。
3.2 多态的威力:写一次,处处可用
定义一个函数,接收 Animal 类型:
def run_twice(animal):
animal.run()
animal.run()传入任何 Animal 或其子类的实例,都能正常工作:
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...3.3 最精彩的部分:新增子类,老代码不动
再定义一个 Tortoise(乌龟):
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')直接传给 run_twice:
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...run_twice 一行代码没改,却支持了新类型!
3.4 这就是"开闭原则"
对扩展开放:可以随时新增 Animal 的子类; 对修改封闭:不需要修改依赖 Animal 的现有代码(如
run_twice)。
生活化理解:家族里新添了曾孙,他会开飞船。家族聚会时喊"大家去干活",曾孙自动用飞船,其他人用各自的方式——喊的人不用管具体是谁,只需要知道"他们都是家族成员,都会干活"。
四、继承树:从 object 开始的家族谱系
所有类最终都继承自 object,形成一棵倒着的树:
┌───────────────┐
│ object │
└───────┬───────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Animal │ │ Plant │
└──────┬──────┘ └──────┬──────┘
│ │
┌────┴────┐ ┌────┴────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ Dog │ │ Cat │ │ Tree │ │Flower│
└──────┘ └──────┘ └──────┘ └──────┘Dog的父类是Animal,Animal的父类是object;Dog继承了Animal的方法,也继承了object的方法(如__init__、__str__等);- 可以一级一级往下继承:爷爷 → 爸爸 → 儿子 → 孙子……
五、鸭子类型:Python 的灵活之道
5.1 静态语言 vs 动态语言
Java(静态语言):函数声明 run_twice(Animal animal),传入的对象必须是 Animal 或其子类,否则编译报错。
Python(动态语言):不强制要求继承关系,只要对象有 run() 方法就行:
class Timer(object):
def run(self):
print('Start...')
run_twice(Timer()) # Start... Start...Timer 和 Animal 毫无继承关系,但它有 run() 方法,所以能传给 run_twice。
5.2 什么是鸭子类型?
"如果一个东西走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
Python 不关心"你是什么类型",只关心"你能做什么"。
经典例子:file-like object
Python 中很多函数接收"文件对象"参数,但实际上任何有 read() 方法的对象都可以:
# 真正的文件
with open('data.txt') as f:
content = f.read()
# 内存中的"假文件"
from io import StringIO
fake_file = StringIO('Hello, world!')
content = fake_file.read()StringIO 不是文件,但它有 read() 方法,所以能被当成文件用。
生活化理解:餐厅招"会切菜的",不管你是有厨师证(继承自 Chef 类)还是自学成才(鸭子类型),只要能把菜切好就录用。
六、常见误区与避坑指南
6.1 误区一:子类重写时忘记调用 super()
class Animal(object):
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
self.breed = breed # ❌ 忘了初始化 name!
dog = Dog('Buddy', 'Golden')
print(dog.name) # AttributeError: 'Dog' object has no attribute 'name'修复:用 super() 调用父类的 __init__:
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # ✅ 先让父类初始化 name
self.breed = breed # 再初始化自己的6.2 误区二:过度使用继承
# ❌ 继承链太深:A → B → C → D → E
class A: pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass继承链超过 3 层就要警惕——代码可读性急剧下降,改顶层影响所有底层。
替代方案:用组合代替继承——把功能拆成小类,通过"包含"关系组装,而不是"继承"关系。
6.3 误区三:以为多态必须靠继承
# ❌ 为了用多态,强行让不相关的类继承同一个父类
class Flyable(object):
def fly(self): pass
class Bird(Flyable): pass
class Airplane(Flyable): pass
class Superman(Flyable): pass在 Python 中,鸭子类型就够了——只要它们都有 fly() 方法,不需要强行继承。
# ✅ 各自独立,但有共同的方法名
class Bird:
def fly(self): print('Bird flies')
class Airplane:
def fly(self): print('Airplane flies')
def let_it_fly(flyable):
flyable.fly()
let_it_fly(Bird()) # Bird flies
let_it_fly(Airplane()) # Airplane flies6.4 误区四:子类覆盖父类方法时改变了参数签名
class Animal(object):
def run(self, speed):
print(f'Running at {speed}')
class Dog(Animal):
def run(self): # ❌ 参数变了!
print('Dog running')
def make_run(animal, speed):
animal.run(speed) # 传入 speed
dog = Dog()
make_run(dog, 10) # TypeError: run() takes 1 positional argument but 2 were given原则:子类重写方法时,参数列表应该和父类兼容(可以更多,不能更少,更不能不兼容)。
七、实战练习
练习 1:形状继承体系
定义一个 Shape 父类,包含方法 area()(返回 0)。然后定义 Rectangle 和 Circle 继承 Shape,各自实现正确的面积计算。
shapes = [Rectangle(3, 4), Circle(5)]
for s in shapes:
print(s.area())
# 12
# 78.5参考答案
class Shape(object):
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2练习 2:利用多态写通知系统
定义 Notifier 父类(有 send(msg) 方法),以及子类 EmailNotifier、SmsNotifier、WechatNotifier,各自实现不同的发送方式。写一个函数 notify_all(notifiers, msg) 给所有通知器发消息。
参考答案
class Notifier(object):
def send(self, msg):
pass
class EmailNotifier(Notifier):
def send(self, msg):
print(f'邮件发送:{msg}')
class SmsNotifier(Notifier):
def send(self, msg):
print(f'短信发送:{msg}')
class WechatNotifier(Notifier):
def send(self, msg):
print(f'微信发送:{msg}')
def notify_all(notifiers, msg):
for n in notifiers:
n.send(msg)
notify_all([EmailNotifier(), SmsNotifier(), WechatNotifier()], '系统维护通知')
# 邮件发送:系统维护通知
# 短信发送:系统维护通知
# 微信发送:系统维护通知新增通知方式(如钉钉)只需加新类,notify_all 完全不用改——这就是开闭原则。
练习 3:找出 bug
下面代码运行会报错,找出原因并修复:
class Animal(object):
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
self.breed = breed
dog = Dog('Buddy', 'Golden')
print(dog.name)参考答案
Dog.__init__ 没有调用 Animal.__init__,导致 name 属性没有被初始化。
修复:
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类初始化
self.breed = breed八、小结
- 继承:子类自动获得父类的全部方法,代码复用的核心手段;
- 重写:子类可以覆盖父类方法,调用时优先使用子类版本;
- 多态:接收父类类型的函数,可以传入任何子类实例,自动调用实际类型的方法——新增子类不影响老代码;
- 继承树:所有类最终追溯到
object,形成倒树结构; - 鸭子类型:Python 的灵活之处——不强制继承,只要"有这个方法"就行;
- 避坑要点:重写时记得
super()、继承链别太深、Python 中多态不必须靠继承、子类方法签名要兼容父类。
继承让我们"站在巨人的肩膀上",多态让我们"写一次代码,支持无数扩展"。但记住:Python 的鸭子类型给了我们更多自由——继承是手段,不是目的。