第7章 7.3 继承与多态
🎯 开场:为什么要学继承?
上一章我们学会了用 @property 给属性加保护罩,就像给房间门装上智能锁——外人只能看,不能直接进去乱翻。
但你有没有遇到过这种情况:你写了一个「学生」类,过两天又要写一个「研究生」类,发现研究生跟学生长得很像,但又不完全一样?
比如学生要管理成绩,研究生除了成绩还要管理研究方向。如果每个类都重新写一遍,代码就又臭又长了。
继承就是来解决这个问题的——让你站在巨人的肩膀上,新类复用老类的代码,只写不一样的地方。
这一章结束后,你就能:
- 用继承让代码少写一半
- 用多态让同一段代码操作不同对象
- 理解 Python 内置工具(如 str、list)为什么能通用
🧱 基础:继承与多态是什么?
7.3.1 继承:儿子继承老子的家产
类比时间:你家开了个煎饼摊,你爸传给你一套配方。你在这个配方基础上,加了自己的秘制酱料。这个过程就是继承——你"继承"了爸的配方,又做了自己的扩展。
在 Python 里:
# 父类(基类)- 老子
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi(self):
print(f"你好,我叫{self.name},今年{self.age}岁")
# 子类(派生类)- 儿子
class Student(Person): # 括号里写父类名,就是继承
def __init__(self, name, age, score):
super().__init__(name, age) # super()调用父类的__init__
self.score = score # 自己独有的属性
def say_hi(self):
super().say_hi()
print(f"我的成绩是{self.score}分")
# 测试
xiaoming = Student("小明", 18, 95)
xiaoming.say_hi()
输出:
你好,我叫小明,今年18岁
我的成绩是95分
解释:
- class Student(Person):Student 继承 Person,相当于"Student 是 Person 的子类"
- super().__init__(name, age):调用父类的初始化方法,避免重复写 self.name = name
- 子类可以有自己独有的属性(如 score),也可以重写父类的方法

7.3.2 方法重写:青出于蓝
子类可以重写(override)父类的方法,这样子类就用自己的逻辑,不用父类的。
class Animal:
def speak(self):
print("动物发出声音")
class Dog(Animal):
def speak(self): # 重写父类的speak方法
print("汪汪汪")
class Cat(Animal):
def speak(self): # 重写父类的speak方法
print("喵喵喵")
dog = Dog()
cat = Cat()
dog.speak() # 输出:汪汪汪
cat.speak() # 输出:喵喵喵
输出:
汪汪汪
喵喵喵
解释: 子类可以完全替换父类的方法实现,这就是"重写"。同一个方法名,不同子类有不同行为。
7.3.3 多态:同一张菜单,不同厨师做
类比时间:你去饭店点"宫保鸡丁",川菜厨师做出来是辣的,粤菜厨师做出来是甜口的。同一个名字(宫保鸡丁),不同厨师做出来味道不一样——这就是多态。
在 Python 里:
def make_them_speak(animal):
"""让任何动物都说话"""
animal.speak()
class Pig(Animal):
def speak(self):
print("哼哼哼")
pig = Pig()
make_them_speak(dog) # 输出:汪汪汪
make_them_speak(cat) # 输出:喵喵喵
make_them_speak(pig) # 输出:哼哼哼
输出:
汪汪汪
喵喵喵
哼哼哼
解释:
- make_them_speak 函数不关心你传的是什么动物,只管调用 .speak()
- 只要对象有 speak 方法,就能用。类型不同,但行为类似——这就是多态

7.3.4 多继承:同时继承多个爹
Python 支持一个子类继承多个父类(多继承),但要注意顺序。
class Flyable:
def fly(self):
print("我会飞!")
class Swimmable:
def swim(self):
print("我会游泳!")
class Duck(Flyable, Swimmable): # 同时继承两个类
def info(self):
print("我是一只鸭子,又会飞又会游")
duck = Duck()
duck.fly()
duck.swim()
duck.info()
输出:
我会飞!
我会游泳!
我是一只鸭子,又会飞又会游
解释: class Duck(Flyable, Swimmable) 同时继承了两个类,duck 对象同时拥有两个父类的能力。
注意:多继承虽好,但不要滥用。如果两个父类有同名的方法,要看 MRO(方法解析顺序)。可以用
类名.__mro__查看。
7.3.5 抽象基类:定义规范,不做具体实现
类比时间:合同模板只规定"必须填姓名、联系电话",但不替你填——抽象基类就是这样,只定义规范,不做具体实现。
from abc import ABC, abstractmethod
class Shape(ABC): # 抽象基类必须继承ABC
@abstractmethod
def area(self): # 抽象方法:子类必须实现
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(5, 3)
print(f"面积:{rect.area()}") # 输出:面积:15
print(f"周长:{rect.perimeter()}") # 输出:周长:16
解释:
- Shape 是抽象基类,规定"所有图形都必须有 area 和 perimeter 方法"
- @abstractmethod 装饰器标记抽象方法,子类必须重写,否则报错
- 如果不实现抽象方法,直接实例化会报 TypeError
🔥 实战:3 个递进小项目
项目 1:员工管理系统(5 分钟)
目标:用继承实现不同岗位的员工,有不同计算工资的方式。
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def get_salary(self):
return self.base_salary
class Programmer(Employee):
def __init__(self, name, base_salary, overtime_hours):
super().__init__(name, base_salary)
self.overtime_hours = overtime_hours
def get_salary(self):
return self.base_salary + self.overtime_hours * 200 # 加班费每小时200
class Sales(Employee):
def __init__(self, name, base_salary, commission):
super().__init__(name, base_salary)
self.commission = commission
def get_salary(self):
return self.base_salary + self.commission # 底薪+提成
# 测试
emp1 = Programmer("张三", 10000, 20)
emp2 = Sales("李四", 8000, 5000)
print(f"{emp1.name} 本月工资:{emp1.get_salary()} 元")
print(f"{emp2.name} 本月工资:{emp2.get_salary()} 元")
输出:
张三 本月工资:14000 元
李四 本月工资:13000 元
解释:两个子类都重写了 get_salary 方法,但调用方式一样——这就是多态的威力。
项目 2:从 JSON 读取数据构建继承结构(15 分钟)
目标:从 JSON 文件读取员工数据,动态创建不同类型的员工对象。
[
{"type": "programmer", "name": "王五", "base_salary": 15000, "overtime_hours": 30},
{"type": "sales", "name": "赵六", "base_salary": 8000, "commission": 8000},
{"type": "employee", "name": "钱七", "base_salary": 6000}
]
import json
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def get_salary(self):
return self.base_salary
def __str__(self):
return f"{self.__class__.__name__}({self.name}, 工资:{self.get_salary()})"
class Programmer(Employee):
def __init__(self, name, base_salary, overtime_hours):
super().__init__(name, base_salary)
self.overtime_hours = overtime_hours
def get_salary(self):
return self.base_salary + self.overtime_hours * 200
class Sales(Employee):
def __init__(self, name, base_salary, commission):
super().__init__(name, base_salary)
self.commission = commission
def get_salary(self):
return self.base_salary + self.commission
# 根据类型字符串创建对象
def create_employee(data):
emp_type = data.get("type", "employee")
if emp_type == "programmer":
return Programmer(data["name"], data["base_salary"], data["overtime_hours"])
elif emp_type == "sales":
return Sales(data["name"], data["base_salary"], data["commission"])
else:
return Employee(data["name"], data["base_salary"])
# 读取JSON并创建对象
json_data = [
{"type": "programmer", "name": "王五", "base_salary": 15000, "overtime_hours": 30},
{"type": "sales", "name": "赵六", "base_salary": 8000, "commission": 8000},
{"type": "employee", "name": "钱七", "base_salary": 6000}
]
employees = [create_employee(data) for data in json_data]
print("=== 全体员工薪资单 ===")
for emp in employees:
print(emp)
输出:
=== 全体员工薪资单 ===
Programmer(王五, 工资:21000)
Sales(赵六, 工资:16000)
Employee(钱七, 工资:6000)
解释:通过字典映射类型字符串到类,然后用统一的接口(get_salary、__str__)处理所有员工——这就是多态的实际应用。
项目 3:做一个简易日志记录器(15 分钟)
目标:做一个日志系统,支持多种输出方式(控制台、文件),通过继承轻松扩展新输出。
from datetime import datetime
class BaseLogger:
"""日志基类"""
def __init__(self, name):
self.name = name
def log(self, level, message):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"[{timestamp}] [{level}] {self.name}: {message}"
self.write(content)
def write(self, content):
"""子类必须实现这个方法"""
raise NotImplementedError
class ConsoleLogger(BaseLogger):
"""控制台日志"""
def write(self, content):
print(content)
class FileLogger(BaseLogger):
"""文件日志"""
def __init__(self, name, filename):
super().__init__(name)
self.filename = filename
def write(self, content):
with open(self.filename, "a", encoding="utf-8") as f:
f.write(content + "\n")
class DualLogger(BaseLogger):
"""同时输出到控制台和文件"""
def __init__(self, name, filename):
super().__init__(name)
self.console = ConsoleLogger(name)
self.file = FileLogger(name, filename)
def write(self, content):
self.console.write(content)
self.file.write(content)
# 使用
logger = DualLogger("系统", "app.log")
logger.log("INFO", "用户登录成功")
logger.log("WARNING", "磁盘空间不足")
logger.log("ERROR", "数据库连接失败")
print("\n--- 文件内容 ---")
with open("app.log", "r", encoding="utf-8") as f:
print(f.read())
输出:
[2024-01-15 10:30:00] [INFO] 系统: 用户登录成功
[2024-01-15 10:30:00] [WARNING] 系统: 磁盘空间不足
[2024-01-15 10:30:00] [ERROR] 系统: 数据库连接失败
--- 文件内容 ---
[2024-01-15 10:30:00] [INFO] 系统: 用户登录成功
[2024-01-15 10:30:00] [WARNING] 系统: 磁盘空间不足
[2024-01-15 10:30:00] [ERROR] 系统: 数据库连接失败
解释:
- 基类 BaseLogger 定义了 log 的通用逻辑(时间戳、格式化)
- 子类只需实现 write 方法,就能自定义输出目标
- 想加新输出方式?新建一个子类就行,不改原有代码——这是"开闭原则"的经典示例
💪 进阶:常见坑 + 调试技巧
坑 1:忘记调用 super().init()
# ❌ 错误写法
class Child(Parent):
def __init__(self, name, extra):
self.name = name # 父类的name没初始化,可能出问题
self.extra = extra
# ✅ 正确写法
class Child(Parent):
def __init__(self, name, extra):
super().__init__(name) # 先调父类的init
self.extra = extra
坑 2:多继承时方法冲突
class A:
def greet(self):
print("A说的你好")
class B:
def greet(self):
print("B说的你好")
class C(A, B):
def greet(self):
super(A, self).greet() # 明确指定从哪个父类找
c = C()
c.greet() # 输出什么?可以用 C.__mro__ 查看
print(C.__mro__) # (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
坑 3:抽象类不能直接实例化
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def run(self):
pass
# ❌ 错误
# base = Base() # TypeError: Can't instantiate abstract class
# ✅ 正确:先继承,再实例化子类
class Impl(Base):
def run(self):
print("运行中")
impl = Impl() # OK
坑 4:重写方法时参数不一致
class Parent:
def process(self, x, y):
return x + y
class Child(Parent):
def process(self, x): # ❌ 参数少了
return x * 2
# child = Child()
# child.process(1, 2) # TypeError
坑 5:用可变对象做默认参数
class Container:
def __init__(self, items=None): # ❌ 常见错误
if items is None:
items = []
self.items = items
调试技巧:
# 查看类的继承链
print(类名.__mro__)
# 查看对象有哪些方法
print(dir(对象))
# 用type()检查对象类型
print(type(obj).__name__)
✏️ 练习题
练习 1(2 分钟):添加新员工类型
- 输入:在项目 1 中添加一个 Manager 类,基本工资 15000,奖金 10000
- 预期输出:Manager("周八", 15000, 10000).get_salary() 返回 25000
- 提示:super().__init__() 传基本工资,自定义逻辑加奖金
练习 2(2 分钟):重写 say_hi
- 输入:在项目 1 的 Programmer 类里重写 say_hi 方法,输出包含"我是一名程序员"
- 预期输出:调用时输出包含"程序员"和"工资"
- 提示:先用 super().say_hi() 再加自己的 print
练习 3(5 分钟):处理新 JSON 数据
- 输入:用项目 2 的方法,添加一个包含 5 个员工的 JSON 列表
- 预期输出:正确输出每个员工的类型和工资
- 提示:只需修改 json_data 变量,其他代码不用动
练习 4(8 分钟):组合日志和员工系统
- 输入:把项目 3 的日志功能加到项目 2,发工资时自动记录日志
- 预期输出:运行后同时看到控制台输出和日志文件内容
- 提示:在 create_employee 里给对象挂一个 logger 属性
练习 5(5 分钟):分析报错
- 输入:运行以下代码会报错,分析原因
class A:
def test(self):
print("A")
class B(A):
def test(self):
print("B")
class C(A):
def test(self):
print("C")
class D(B, C):
pass
D().test() # 输出什么?为什么?
- 预期输出:解释 MRO 的工作原理
- 提示:用
D.__mro__查看顺序
作业:做一个宠物店管理系统
需求:管理不同类型的宠物,有不同计算价格的方式。
功能点:
1. 宠物基类,包含名字、价格、计算总价方法
2. 猫、狗、鸟三个子类,继承基类
3. 从 JSON 读取宠物数据,动态创建对象
4. 支持多态:同一个函数处理所有宠物
加分项:
1. 添加新宠物类型(兔子、鱼)只需加代码不改动原有代码
2. 用抽象基类规范所有宠物必须有"计算总价"方法
验收标准:
- 能运行,输出所有宠物的名称和价格
- 代码有注释
- 提交方式:评论区贴代码
📚 总结
这一章学了 3 件事:
1. 继承:子类复用父类代码,用 super() 调用父类方法
2. 方法重写:子类替换父类方法实现
3. 多态:同一接口,不同对象有不同行为
延伸资源:
- 官方文档:https://docs.python.org/3/tutorial/classes.html#inheritance
- 《Python编程:从入门到实践》第 9 章
- 视频:B 站小甲鱼 Python 教程第 27-28 讲
互动钩子:你在实际项目中用过继承吗?是怎么组织的?评论区聊聊老粉优先回复!
预告:下一章我们要学习类的"特殊方法"——
__str__、__len__、__add__这些带双下划线的方法,它们能让你的对象支持len()、+、print()等内置操作。敬请期待!

评论(0)