Python(三十) 类的多态详解
1. 什么是多态
多态是面向对象编程中的重要思想。
通俗地说:
多态就是同一个操作,作用在不同对象上,可以产生不同的结果。
也可以这样理解:
同一个方法名,不同对象有不同实现。
调用时不用关心对象的具体类型,只关心它能不能完成这个动作。
例如:
不同图形都可以计算面积:
矩形的面积 = 宽 * 高
圆形的面积 = 3.14 * 半径 * 半径
三角形的面积 = 底 * 高 / 2
它们都可以有一个共同的方法:
area()
但是每种图形计算面积的方式不同。
这就是多态。
2. 为什么需要多态
假设我们有矩形和圆形两个类。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
它们都有 area() 方法。
我们可以写一个函数,专门用来打印面积:
def print_area(shape):
print(shape.area())
然后传入不同对象:
rect = Rectangle(3, 4)
circle = Circle(5)
print_area(rect)
print_area(circle)
输出:
12
78.5
print_area() 不需要知道传进来的是矩形还是圆形。
它只需要知道:
这个对象有 area() 方法。
这就是多态带来的好处:
写代码时更灵活。
函数可以处理多种不同对象。
新增类型时,原来的调用代码不需要大改。
3. 多态的核心思想
多态的核心思想可以总结为:
同一接口,不同实现。
这里的“接口”可以先简单理解为:
大家约定好的方法名。
例如:
所有图形都提供 area() 方法。
所有支付方式都提供 pay() 方法。
所有消息通知方式都提供 send() 方法。
所有文件导出器都提供 export() 方法。
不同类都使用同一个方法名,但内部实现不同。
示例:
class Alipay:
def pay(self, money):
print(f"使用支付宝支付 {money} 元")
class WeChatPay:
def pay(self, money):
print(f"使用微信支付 {money} 元")
class BankCardPay:
def pay(self, money):
print(f"使用银行卡支付 {money} 元")
它们都有:
pay()
但是具体支付方式不同。
4. 多态和继承的关系
在很多面向对象语言中,多态通常和继承一起出现。
例如:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
这里:
Shape 是父类。
Rectangle 和 Circle 是子类。
父类中定义 area()。
子类中重写 area()。
当我们调用:
shape.area()
不同子类对象会执行各自的 area() 方法。
这就是继承中的多态。
5. 一个基础多态例子
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def print_area(shape):
print(shape.area())
rect = Rectangle(3, 4)
circle = Circle(5)
print_area(rect)
print_area(circle)
输出:
12
78.5
这里的 print_area() 就体现了多态:
def print_area(shape):
print(shape.area())
它不关心 shape 具体是什么类型。
只要 shape 有 area() 方法,就可以使用。
6. 多态中的方法重写
多态经常依赖方法重写。
方法重写指的是:
子类重新定义父类中已有的方法。
示例:
class Notification:
def send(self, message):
print("发送通知:", message)
class EmailNotification(Notification):
def send(self, message):
print("发送邮件通知:", message)
class SmsNotification(Notification):
def send(self, message):
print("发送短信通知:", message)
父类和子类都有 send() 方法。
子类中的 send() 会覆盖父类中的 send()。
email = EmailNotification()
sms = SmsNotification()
email.send("订单已创建")
sms.send("验证码是 123456")
输出:
发送邮件通知: 订单已创建
发送短信通知: 验证码是 123456
7. 用统一函数调用不同对象
多态的常见写法是:
定义一个统一函数,让不同对象传进去执行各自的方法。
示例:
class EmailNotification:
def send(self, message):
print("邮件通知:", message)
class SmsNotification:
def send(self, message):
print("短信通知:", message)
class AppNotification:
def send(self, message):
print("App 通知:", message)
def notify(sender, message):
sender.send(message)
email = EmailNotification()
sms = SmsNotification()
app = AppNotification()
notify(email, "欢迎注册")
notify(sms, "验证码是 8888")
notify(app, "你有一条新消息")
输出:
邮件通知: 欢迎注册
短信通知: 验证码是 8888
App 通知: 你有一条新消息
notify() 不需要分别判断:
if 是邮件:
...
elif 是短信:
...
elif 是 App:
...
它只统一调用:
sender.send(message)
这就是多态让代码变简洁的地方。
8. 没有多态时的写法
先看没有多态时可能会怎么写。
class EmailNotification:
def send_email(self, message):
print("邮件通知:", message)
class SmsNotification:
def send_sms(self, message):
print("短信通知:", message)
def notify(sender, message, sender_type):
if sender_type == "email":
sender.send_email(message)
elif sender_type == "sms":
sender.send_sms(message)
调用:
email = EmailNotification()
sms = SmsNotification()
notify(email, "欢迎注册", "email")
notify(sms, "验证码是 8888", "sms")
这样的问题是:
每增加一种通知方式,notify() 函数就要修改。
不同类的方法名也不统一。
9. 使用多态后的写法
class EmailNotification:
def send(self, message):
print("邮件通知:", message)
class SmsNotification:
def send(self, message):
print("短信通知:", message)
class AppNotification:
def send(self, message):
print("App 通知:", message)
def notify(sender, message):
sender.send(message)
调用:
email = EmailNotification()
sms = SmsNotification()
app = AppNotification()
notify(email, "欢迎注册")
notify(sms, "验证码是 8888")
notify(app, "你有一条新消息")
这样写的好处:
所有通知类都统一使用 send()。
notify() 不需要知道具体通知类型。
新增通知方式时,notify() 通常不用改。
这就是多态非常重要的价值。
10. Python 中的鸭子类型
Python 的多态有一个非常重要的特点:鸭子类型。
鸭子类型的意思是:
如果一个对象能完成某个行为,我们就把它当成能完成这个行为的对象来使用。
在 Python 中,很多时候不关心对象是不是某个父类的子类。
更关心的是:
这个对象有没有我需要的方法?
例如:
class PdfExporter:
def export(self, data):
print("导出 PDF:", data)
class ExcelExporter:
def export(self, data):
print("导出 Excel:", data)
class CsvExporter:
def export(self, data):
print("导出 CSV:", data)
def export_data(exporter, data):
exporter.export(data)
这些类没有共同父类。
但是它们都有:
export()
所以都可以传给 export_data():
data = "学生成绩表"
export_data(PdfExporter(), data)
export_data(ExcelExporter(), data)
export_data(CsvExporter(), data)
输出:
导出 PDF: 学生成绩表
导出 Excel: 学生成绩表
导出 CSV: 学生成绩表
这就是 Python 中非常常见的多态写法。
11. 鸭子类型和继承多态的区别
继承多态通常是:
子类继承同一个父类。
子类重写父类方法。
统一调用父类约定的方法。
鸭子类型通常是:
不一定有共同父类。
只要对象有需要的方法,就可以使用。
对比表:
| 对比项 | 继承中的多态 | 鸭子类型 |
|---|---|---|
| 是否需要共同父类 | 通常需要 | 不一定需要 |
| 关注点 | 对象是不是某类的子类 | 对象有没有需要的方法 |
| 常见写法 | 子类重写父类方法 | 不同类实现同名方法 |
| Python 中是否常见 | 常见 | 非常常见 |
示例:
def run(task):
task.execute()
只要传入的对象有 execute() 方法,就可以使用。
这就是鸭子类型的思路。
12. 多态和 if 判断的关系
多态经常可以减少大量 if...elif...else。
没有多态:
def calculate_area(shape):
if shape["type"] == "rectangle":
return shape["width"] * shape["height"]
elif shape["type"] == "circle":
return 3.14 * shape["radius"] * shape["radius"]
elif shape["type"] == "triangle":
return shape["base"] * shape["height"] / 2
使用多态:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height / 2
def calculate_area(shape):
return shape.area()
调用:
shapes = [
Rectangle(3, 4),
Circle(5),
Triangle(6, 8)
]
for shape in shapes:
print(calculate_area(shape))
输出:
12
78.5
24.0
这样代码更容易扩展。
如果以后新增一个正方形类,只要它有 area() 方法,calculate_area() 不需要修改。
13. 多态的常见结构
多态常见结构如下:
class A:
def do(self):
pass
class B(A):
def do(self):
print("B 的实现")
class C(A):
def do(self):
print("C 的实现")
def run(obj):
obj.do()
调用:
run(B())
run(C())
结果:
同一个 run() 函数,
传入不同对象,
执行不同的 do() 方法。
记忆:
同名方法,不同表现。
14. 多态案例:支付方式
不同支付方式都可以支付,但具体实现不同。
class Alipay:
def pay(self, money):
print(f"支付宝支付 {money} 元")
class WeChatPay:
def pay(self, money):
print(f"微信支付 {money} 元")
class BankCardPay:
def pay(self, money):
print(f"银行卡支付 {money} 元")
def checkout(payment, money):
payment.pay(money)
checkout(Alipay(), 100)
checkout(WeChatPay(), 200)
checkout(BankCardPay(), 300)
输出:
支付宝支付 100 元
微信支付 200 元
银行卡支付 300 元
这里:
checkout() 是统一结账函数。
不同支付对象都有 pay() 方法。
checkout() 不关心具体支付方式。
如果新增一种支付方式:
class CreditPay:
def pay(self, money):
print(f"信用支付 {money} 元")
直接使用:
checkout(CreditPay(), 500)
不需要修改 checkout()。
这就是多态带来的扩展能力。
15. 多态案例:消息通知
class Email:
def send(self, message):
print(f"邮件发送:{message}")
class Sms:
def send(self, message):
print(f"短信发送:{message}")
class AppPush:
def send(self, message):
print(f"App 推送:{message}")
def send_message(sender, message):
sender.send(message)
senders = [Email(), Sms(), AppPush()]
for sender in senders:
send_message(sender, "系统维护通知")
输出:
邮件发送:系统维护通知
短信发送:系统维护通知
App 推送:系统维护通知
这个例子适合课堂讲解:
同一个 send_message()。
传入不同通知对象。
执行不同通知逻辑。
16. 多态案例:文件导出
class PdfExporter:
def export(self, data):
print(f"导出 PDF 文件:{data}")
class ExcelExporter:
def export(self, data):
print(f"导出 Excel 文件:{data}")
class HtmlExporter:
def export(self, data):
print(f"导出 HTML 文件:{data}")
def export_report(exporter, data):
exporter.export(data)
report = "本月销售报表"
export_report(PdfExporter(), report)
export_report(ExcelExporter(), report)
export_report(HtmlExporter(), report)
输出:
导出 PDF 文件:本月销售报表
导出 Excel 文件:本月销售报表
导出 HTML 文件:本月销售报表
如果新增 JSON 导出:
class JsonExporter:
def export(self, data):
print(f"导出 JSON 文件:{data}")
直接调用:
export_report(JsonExporter(), report)
原来的 export_report() 不需要改变。
17. 多态案例:图形面积
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height / 2
def total_area(shapes):
total = 0
for shape in shapes:
total += shape.area()
return total
shapes = [
Rectangle(3, 4),
Circle(5),
Triangle(6, 8)
]
print(total_area(shapes))
输出:
114.5
这里:
shapes 列表中可以放不同类型的图形对象。
这些对象都有 area() 方法。
total_area() 统一调用 shape.area()。
不同对象自己决定面积如何计算。
18. 多态案例:员工薪资
不同员工的工资计算方式可能不同。
class FullTimeEmployee:
def __init__(self, name, monthly_salary):
self.name = name
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
class PartTimeEmployee:
def __init__(self, name, hours, hourly_rate):
self.name = name
self.hours = hours
self.hourly_rate = hourly_rate
def calculate_salary(self):
return self.hours * self.hourly_rate
class SalesEmployee:
def __init__(self, name, base_salary, commission):
self.name = name
self.base_salary = base_salary
self.commission = commission
def calculate_salary(self):
return self.base_salary + self.commission
def print_salary(employee):
print(f"{employee.name} 的工资是 {employee.calculate_salary()}")
employees = [
FullTimeEmployee("张三", 8000),
PartTimeEmployee("李四", 80, 50),
SalesEmployee("王五", 5000, 3000)
]
for employee in employees:
print_salary(employee)
输出:
张三 的工资是 8000
李四 的工资是 4000
王五 的工资是 8000
这里:
不同员工都有 calculate_salary()。
但是计算方式不同。
print_salary() 不关心员工类型。
19. 多态和抽象基类
在教学多态时,还可以了解抽象基类。
抽象基类可以用来规定子类必须实现某些方法。
Python 中可以使用 abc 模块。
示例:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
这里:
Shape 是抽象基类。
area() 是抽象方法。
继承 Shape 的子类必须实现 area()。
完整示例:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
使用:
shapes = [Rectangle(3, 4), Circle(5)]
for shape in shapes:
print(shape.area())
输出:
12
78.5
20. 抽象基类的作用
抽象基类主要有两个作用:
1. 规定子类必须实现哪些方法。
2. 让代码结构更清晰。
例如:
class Payment(ABC):
@abstractmethod
def pay(self, money):
pass
意思是:
所有支付类都必须有 pay() 方法。
子类:
class Alipay(Payment):
def pay(self, money):
print(f"支付宝支付 {money} 元")
如果子类没有实现 pay():
class BadPayment(Payment):
pass
创建对象时会报错:
bad = BadPayment()
这可以帮助我们早点发现问题。
21. 抽象方法和普通方法的区别
普通方法:
class Shape:
def area(self):
return 0
子类可以不重写。
抽象方法:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
子类必须重写。
对比表:
| 对比项 | 普通方法 | 抽象方法 |
|---|---|---|
| 是否必须被子类实现 | 不必须 | 必须 |
是否需要 abc 模块 |
不需要 | 需要 |
| 适合场景 | 提供默认行为 | 规定统一接口 |
初学阶段可以先掌握普通多态。
抽象基类适合在学生已经理解继承和方法重写后再讲。
22. NotImplementedError 写法
除了抽象基类,还可以在父类方法中主动抛出 NotImplementedError。
示例:
class Shape:
def area(self):
raise NotImplementedError("子类必须实现 area 方法")
子类:
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
如果某个子类忘记实现 area():
class UnknownShape(Shape):
pass
shape = UnknownShape()
shape.area()
会报错并提示:
子类必须实现 area 方法
这种写法也能提醒子类实现必要方法。
23. 多态和类型判断
使用多态时,通常不需要频繁写 type() 判断。
不推荐:
def print_area(shape):
if type(shape) == Rectangle:
print(shape.area())
elif type(shape) == Circle:
print(shape.area())
更推荐:
def print_area(shape):
print(shape.area())
原因:
多态的重点就是让对象自己决定如何执行。
如果到处判断类型,就失去了多态的意义。
当然,有些场景确实需要类型判断。
但在多态设计中,优先考虑统一方法名,而不是到处写类型分支。
24. isinstance() 在多态中的使用
有时我们想确认对象是否属于某个父类,可以使用 isinstance()。
示例:
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def area(self):
return 12
rect = Rectangle()
print(isinstance(rect, Rectangle))
print(isinstance(rect, Shape))
输出:
True
True
因为:
rect 是 Rectangle 对象。
Rectangle 继承 Shape。
所以 rect 也可以看作 Shape 对象。
但是要注意:
Python 多态不一定要求 isinstance() 判断。
只要对象有需要的方法,通常就可以使用。
25. hasattr() 简单了解
如果想判断一个对象是否有某个方法,可以使用 hasattr()。
示例:
class PdfExporter:
def export(self, data):
print("导出 PDF:", data)
exporter = PdfExporter()
print(hasattr(exporter, "export"))
print(hasattr(exporter, "save"))
输出:
True
False
可以这样写:
def export_data(exporter, data):
if hasattr(exporter, "export"):
exporter.export(data)
else:
print("对象没有 export 方法")
不过教学时要提醒:
hasattr() 可以检查能力,但不要滥用。
很多时候让错误自然暴露,反而更容易发现代码问题。
26. 多态的优点
多态的优点主要有:
26.1 减少重复代码
多个类使用同一套调用方式。
def process(obj):
obj.run()
不用为每种类型都写一个函数。
26.2 提高扩展性
新增类时,只要实现同名方法,原来的调用代码通常不用修改。
例如新增支付方式:
class NewPay:
def pay(self, money):
print(f"新支付方式支付 {money} 元")
原来的结账函数仍然可以用:
checkout(NewPay(), 100)
26.3 让代码更灵活
函数可以接收多种不同对象。
def send_message(sender, message):
sender.send(message)
只要对象有 send() 方法,就可以传入。
26.4 让职责更清楚
每个类负责自己的实现。
调用方只负责调用统一方法。
调用方:调用 pay()
支付宝类:实现支付宝怎么支付
微信类:实现微信怎么支付
银行卡类:实现银行卡怎么支付
27. 多态的适用场景
多态适合这些场景:
多个类有相同的行为名称,但具体实现不同。
希望写统一的函数处理不同对象。
希望新增类型时少改旧代码。
希望减少大量 if 类型判断。
常见例子:
不同图形计算面积
不同支付方式付款
不同通知方式发送消息
不同文件格式导出数据
不同员工类型计算工资
不同任务执行 execute()
28. 多态不适合的场景
不是所有地方都需要多态。
如果只有一个类,或者逻辑非常简单,就不一定需要设计多态。
例如:
class Student:
def study(self):
print("学习")
如果程序中只有一种学生对象,并没有多种不同实现,就不需要强行讲多态。
不适合:
为了使用面向对象而强行设计很多类。
为了减少一两个 if 就写复杂继承结构。
不同类之间根本没有统一行为。
教学总结:
多态适合“多种对象,同一行为,不同实现”的场景。
29. 多态和方法重载
有些语言中有方法重载。
方法重载通常指:
同一个类中,方法名相同,但参数不同。
例如在某些语言中可以写:
add(int a, int b)
add(float a, float b)
Python 不支持这种传统意义上的方法重载。
如果在 Python 中写两个同名方法:
class Calculator:
def add(self, a, b):
return a + b
def add(self, a, b, c):
return a + b + c
后面的 add() 会覆盖前面的 add()。
所以:
calc = Calculator()
print(calc.add(1, 2, 3))
可以运行。
但:
print(calc.add(1, 2))
会报错。
Python 中如果想让参数灵活,可以使用默认参数或 *args。
class Calculator:
def add(self, *args):
return sum(args)
calc = Calculator()
print(calc.add(1, 2))
print(calc.add(1, 2, 3))
输出:
3
6
注意:
多态不是方法重载。
多态强调不同对象对同一方法有不同实现。
30. 多态和方法重写
方法重写是多态的重要基础。
方法重写:
子类重新实现父类中已有的方法。
多态:
调用同一个方法时,不同对象表现出不同结果。
示例:
class Report:
def export(self):
print("导出通用报表")
class PdfReport(Report):
def export(self):
print("导出 PDF 报表")
class ExcelReport(Report):
def export(self):
print("导出 Excel 报表")
def export_report(report):
report.export()
export_report(PdfReport())
export_report(ExcelReport())
输出:
导出 PDF 报表
导出 Excel 报表
这里:
PdfReport 和 ExcelReport 重写 export()。
export_report() 调用 export() 时产生不同结果。
这就是多态。
31. 多态中的参数一致性
为了方便统一调用,不同类中的同名方法,参数最好保持一致。
推荐:
class Email:
def send(self, message):
print("邮件:", message)
class Sms:
def send(self, message):
print("短信:", message)
统一调用:
def notify(sender, message):
sender.send(message)
不推荐:
class Email:
def send(self, title, content):
print(title, content)
class Sms:
def send(self, message):
print(message)
这样 notify() 很难统一调用。
教学总结:
要实现多态,同名方法的参数设计最好保持一致。
32. 多态中的返回值一致性
除了参数,返回值的含义也最好一致。
例如,所有 area() 都返回数字。
class Rectangle:
def area(self):
return 12
class Circle:
def area(self):
return 78.5
不推荐:
class Rectangle:
def area(self):
return 12
class Circle:
def area(self):
print("面积是 78.5")
一个返回数字,一个直接打印,会让调用方很难统一处理。
推荐:
def print_area(shape):
print(shape.area())
前提是每个 area() 都返回面积。
教学总结:
同一个多态方法,返回值含义最好一致。
33. 多态中的统一接口
多态依赖统一接口。
这里的接口可以简单理解为:
统一的方法名、参数和返回值约定。
例如导出器接口:
方法名:export
参数:data
返回值:可以无返回值,也可以返回导出路径
所有导出器都遵守这个约定:
class PdfExporter:
def export(self, data):
print("PDF:", data)
class ExcelExporter:
def export(self, data):
print("Excel:", data)
统一调用:
def export_data(exporter, data):
exporter.export(data)
如果某个类不遵守约定:
class BadExporter:
def save(self, data):
print("保存:", data)
那么它不能直接传给:
export_data(BadExporter(), "数据")
因为它没有 export() 方法。
34. 多态和开放封闭原则
多态可以帮助我们写出更容易扩展的代码。
有一个常见设计原则叫开放封闭原则。
简单理解:
对扩展开放,对修改关闭。
意思是:
需要新增功能时,尽量新增代码。
尽量少修改已经稳定的旧代码。
看支付案例:
def checkout(payment, money):
payment.pay(money)
如果新增一种支付方式:
class GiftCardPay:
def pay(self, money):
print(f"礼品卡支付 {money} 元")
直接调用:
checkout(GiftCardPay(), 100)
checkout() 不需要修改。
这就是多态带来的扩展优势。
35. 常见错误 1:方法名不统一
错误示例:
class Email:
def send_email(self, message):
print(message)
class Sms:
def send_sms(self, message):
print(message)
这样很难写统一函数。
更推荐:
class Email:
def send(self, message):
print(message)
class Sms:
def send(self, message):
print(message)
统一调用:
def notify(sender, message):
sender.send(message)
36. 常见错误 2:方法参数不一致
错误示例:
class PdfExporter:
def export(self, data):
print(data)
class ExcelExporter:
def export(self, data, sheet_name):
print(data, sheet_name)
统一调用时会很麻烦:
def export_data(exporter, data):
exporter.export(data)
PdfExporter 可以用。
ExcelExporter 会因为少传 sheet_name 而报错。
解决办法:
class ExcelExporter:
def export(self, data):
print("默认工作表:", data)
或者使用默认参数:
class ExcelExporter:
def export(self, data, sheet_name="Sheet1"):
print(sheet_name, data)
37. 常见错误 3:返回值不一致
错误示例:
class Rectangle:
def area(self):
return 12
class Circle:
def area(self):
print(78.5)
如果统一计算总面积:
total += shape.area()
Circle.area() 没有返回数字,就会出问题。
正确做法:
class Circle:
def area(self):
return 78.5
38. 常见错误 4:过度依赖 type 判断
不推荐:
def pay(payment, money):
if type(payment) == Alipay:
payment.pay(money)
elif type(payment) == WeChatPay:
payment.pay(money)
更推荐:
def pay(payment, money):
payment.pay(money)
原因:
多态就是为了让不同对象自己实现同一行为。
如果每次都判断类型,扩展性会变差。
39. 常见错误 5:父类方法只写 pass,子类忘记重写
示例:
class Shape:
def area(self):
pass
class Rectangle(Shape):
pass
调用:
rect = Rectangle()
print(rect.area())
输出:
None
这可能不是我们想要的结果。
更清晰的写法:
class Shape:
def area(self):
raise NotImplementedError("子类必须实现 area 方法")
这样子类忘记实现时,会直接报错提醒。
40. 常见错误 6:以为所有多态都必须继承
Python 中不一定必须继承才能实现多态。
例如:
class PdfExporter:
def export(self, data):
print("PDF", data)
class ExcelExporter:
def export(self, data):
print("Excel", data)
它们没有共同父类,但都有 export() 方法。
所以:
def export_data(exporter, data):
exporter.export(data)
仍然可以工作。
教学总结:
Python 多态既可以基于继承,也可以基于鸭子类型。
41. 常见错误 7:为了多态强行设计复杂类
不推荐为了一个很小的需求强行写很多类。
例如,只有一种支付方式时:
class Payment:
pass
可能没有必要。
多态适合:
已经有多种类型。
或者很明显将来会扩展多种类型。
教学中可以说:
多态是解决复杂变化的工具。
不是所有简单问题都需要它。
42. 常见错误 8:统一方法名但含义不同
不推荐:
class Printer:
def run(self):
print("打印文件")
class Calculator:
def run(self):
print("计算工资")
虽然都有 run(),但含义差别很大。
如果放在一起统一调用,可能让代码含义变得不清楚。
多态中的同名方法,最好表达同一种行为。
例如:
export()
send()
pay()
area()
calculate_salary()
这些方法名比较明确。
43. 注意事项 1:统一方法名
想让多个类支持多态,首先要统一方法名。
例如:
支付统一叫 pay()
发送统一叫 send()
导出统一叫 export()
计算面积统一叫 area()
执行任务统一叫 execute()
这样调用方才能写统一代码。
44. 注意事项 2:统一参数
同名方法的参数最好保持一致。
推荐:
def send(self, message):
...
所有通知类都这样写。
不推荐有的写:
send(self, message)
有的写:
send(self, title, content)
这样统一调用会变困难。
45. 注意事项 3:统一返回值含义
同一个多态方法,返回值最好表达同一种含义。
例如:
area() 返回面积数字。
pay() 返回支付是否成功。
export() 返回导出文件路径。
不要有的返回数字,有的打印字符串,有的返回 None。
除非调用方不关心返回值。
46. 注意事项 4:让每个类负责自己的实现
多态的重点是:
调用方统一调用。
具体类自己实现。
例如:
def checkout(payment, money):
payment.pay(money)
checkout() 不应该关心:
支付宝怎么支付
微信怎么支付
银行卡怎么支付
这些细节应该放在各自类的 pay() 方法中。
47. 注意事项 5:新增类型时尽量不修改旧函数
如果代码设计得好,新增类型时通常只需要新增类。
例如新增一种导出方式:
class XmlExporter:
def export(self, data):
print("导出 XML:", data)
原来的函数不变:
def export_data(exporter, data):
exporter.export(data)
这说明多态设计比较自然。
48. 注意事项 6:抽象基类适合中后期学习
抽象基类很有用,但对初学者来说概念稍微多一些。
教学顺序建议:
1. 先讲同名方法。
2. 再讲统一函数调用不同对象。
3. 再讲继承中的方法重写。
4. 最后介绍抽象基类。
这样学生更容易理解。
49. 注意事项 7:错误提示要清楚
如果父类方法要求子类必须实现,可以使用:
raise NotImplementedError("子类必须实现 xxx 方法")
比单纯写:
pass
更容易发现问题。
示例:
class Task:
def execute(self):
raise NotImplementedError("子类必须实现 execute 方法")
50. 注意事项 8:多态不等于随便传对象
多态不是说任何对象都能传进去。
比如:
def print_area(shape):
print(shape.area())
这个函数要求传入的对象必须有 area() 方法。
如果传入:
print_area("hello")
会报错。
原因:
字符串没有 area() 方法。
所以多态的前提是:
对象必须具备调用方需要的能力。
51. 综合案例:任务执行器
class EmailTask:
def execute(self):
print("执行邮件发送任务")
class BackupTask:
def execute(self):
print("执行数据备份任务")
class CleanTask:
def execute(self):
print("执行缓存清理任务")
def run_task(task):
task.execute()
tasks = [
EmailTask(),
BackupTask(),
CleanTask()
]
for task in tasks:
run_task(task)
输出:
执行邮件发送任务
执行数据备份任务
执行缓存清理任务
这个案例中:
不同任务都有 execute()。
run_task() 统一调用 execute()。
每个任务自己决定如何执行。
52. 综合案例:报表导出系统
class PdfReport:
def export(self, data):
return f"PDF 报表:{data}"
class ExcelReport:
def export(self, data):
return f"Excel 报表:{data}"
class CsvReport:
def export(self, data):
return f"CSV 报表:{data}"
def export_all(reports, data):
results = []
for report in reports:
result = report.export(data)
results.append(result)
return results
reports = [
PdfReport(),
ExcelReport(),
CsvReport()
]
data = "学生成绩数据"
results = export_all(reports, data)
for item in results:
print(item)
输出:
PDF 报表:学生成绩数据
Excel 报表:学生成绩数据
CSV 报表:学生成绩数据
这个案例适合讲:
同一个列表中可以放不同对象。
只要这些对象都有 export() 方法,就可以统一处理。
53. 综合案例:订单优惠计算
不同优惠规则有不同计算方式。
class NoDiscount:
def calculate(self, price):
return price
class FixedDiscount:
def __init__(self, discount):
self.discount = discount
def calculate(self, price):
return price - self.discount
class PercentDiscount:
def __init__(self, percent):
self.percent = percent
def calculate(self, price):
return price * self.percent
def final_price(price, discount_strategy):
return discount_strategy.calculate(price)
price = 100
print(final_price(price, NoDiscount()))
print(final_price(price, FixedDiscount(20)))
print(final_price(price, PercentDiscount(0.8)))
输出:
100
80
80.0
这里:
不同优惠策略都有 calculate() 方法。
final_price() 不关心具体优惠类型。
54. 综合案例:表单字段校验
class RequiredValidator:
def validate(self, value):
return value != ""
class LengthValidator:
def __init__(self, min_length):
self.min_length = min_length
def validate(self, value):
return len(value) >= self.min_length
class NumberValidator:
def validate(self, value):
return value.isdigit()
def check_value(value, validators):
for validator in validators:
if not validator.validate(value):
return False
return True
validators = [
RequiredValidator(),
LengthValidator(6),
NumberValidator()
]
print(check_value("123456", validators))
print(check_value("abc123", validators))
print(check_value("", validators))
输出:
True
False
False
这个案例中:
不同校验器都有 validate() 方法。
check_value() 统一调用 validate()。
每个校验器负责自己的规则。
55. 课堂练习 1:图形面积
要求:
- 定义
Rectangle类,有area()方法。 - 定义
Circle类,有area()方法。 - 定义
print_area(shape)函数,打印面积。 - 分别传入矩形对象和圆形对象。
参考答案:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def print_area(shape):
print(shape.area())
print_area(Rectangle(3, 4))
print_area(Circle(5))
56. 课堂练习 2:支付方式
要求:
- 定义
Alipay类,有pay()方法。 - 定义
WeChatPay类,有pay()方法。 - 定义
checkout(payment, money)函数。 - 传入不同支付对象,完成支付。
参考答案:
class Alipay:
def pay(self, money):
print(f"支付宝支付 {money} 元")
class WeChatPay:
def pay(self, money):
print(f"微信支付 {money} 元")
def checkout(payment, money):
payment.pay(money)
checkout(Alipay(), 100)
checkout(WeChatPay(), 200)
57. 课堂练习 3:消息通知
要求:
- 定义
Email类,有send()方法。 - 定义
Sms类,有send()方法。 - 定义
send_message(sender, message)函数。 - 使用列表统一发送通知。
参考答案:
class Email:
def send(self, message):
print("邮件:", message)
class Sms:
def send(self, message):
print("短信:", message)
def send_message(sender, message):
sender.send(message)
senders = [Email(), Sms()]
for sender in senders:
send_message(sender, "系统通知")
58. 课堂练习 4:文件导出
要求:
- 定义
PdfExporter类,有export()方法。 - 定义
ExcelExporter类,有export()方法。 - 定义
export_data(exporter, data)函数。 - 传入不同导出器对象。
参考答案:
class PdfExporter:
def export(self, data):
print("导出 PDF:", data)
class ExcelExporter:
def export(self, data):
print("导出 Excel:", data)
def export_data(exporter, data):
exporter.export(data)
export_data(PdfExporter(), "成绩表")
export_data(ExcelExporter(), "成绩表")
59. 课堂练习 5:员工工资
要求:
- 定义
FullTimeEmployee类,有calculate_salary()方法。 - 定义
PartTimeEmployee类,有calculate_salary()方法。 - 定义
print_salary(employee)函数。 - 传入不同员工对象。
参考答案:
class FullTimeEmployee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def calculate_salary(self):
return self.salary
class PartTimeEmployee:
def __init__(self, name, hours, hourly_rate):
self.name = name
self.hours = hours
self.hourly_rate = hourly_rate
def calculate_salary(self):
return self.hours * self.hourly_rate
def print_salary(employee):
print(f"{employee.name} 的工资是 {employee.calculate_salary()}")
print_salary(FullTimeEmployee("张三", 8000))
print_salary(PartTimeEmployee("李四", 80, 50))
60. 课堂练习 6:使用抽象基类
要求:
- 使用
ABC定义抽象类Payment。 - 在
Payment中定义抽象方法pay()。 - 定义
Alipay类继承Payment并实现pay()。
参考答案:
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def pay(self, money):
print(f"支付宝支付 {money} 元")
payment = Alipay()
payment.pay(100)
61. 课堂练习 7:判断输出
阅读代码,判断输出结果:
class PdfExporter:
def export(self, data):
print("PDF", data)
class ExcelExporter:
def export(self, data):
print("Excel", data)
def export_data(exporter, data):
exporter.export(data)
export_data(PdfExporter(), "报表")
export_data(ExcelExporter(), "报表")
答案:
PDF 报表
Excel 报表
解释:
两个对象都有 export() 方法。
export_data() 统一调用 export()。
不同对象执行不同实现。
62. 课堂练习 8:找错误
下面代码有什么问题?
class Email:
def send_email(self, message):
print(message)
class Sms:
def send(self, message):
print(message)
def notify(sender, message):
sender.send(message)
notify(Email(), "你好")
答案:
Email 类中没有 send() 方法,只有 send_email() 方法。
notify() 调用的是 sender.send(message),所以会报错。
修改方式:
class Email:
def send(self, message):
print(message)
63. 课堂练习 9:补全代码
补全代码,让不同任务都能被 run_task() 执行。
题目:
class PrintTask:
def execute(self):
print("打印任务")
class SaveTask:
# 请补全这里
def run_task(task):
task.execute()
run_task(PrintTask())
run_task(SaveTask())
参考答案:
class PrintTask:
def execute(self):
print("打印任务")
class SaveTask:
def execute(self):
print("保存任务")
def run_task(task):
task.execute()
run_task(PrintTask())
run_task(SaveTask())
64. 课堂练习 10:判断是否适合多态
下面哪些场景适合使用多态?
1. 不同支付方式都要付款。
2. 不同图形都要计算面积。
3. 只有一个学生类,只保存姓名和年龄。
4. 不同文件格式都要导出数据。
5. 不同通知方式都要发送消息。
参考答案:
适合:
1. 不同支付方式都要付款。
2. 不同图形都要计算面积。
4. 不同文件格式都要导出数据。
5. 不同通知方式都要发送消息。
不太需要:
3. 只有一个学生类,只保存姓名和年龄。
原因:
多态适合多种对象拥有同一行为,但具体实现不同的场景。
65. 教学总结
多态是 Python 面向对象编程中的重要思想。
一句话总结:
多态就是同一个方法名,作用在不同对象上,产生不同结果。
核心知识:
- 多态强调同一接口,不同实现。
- 多态常和继承、方法重写一起使用。
- Python 中也常通过鸭子类型实现多态。
- 多态可以减少类型判断。
- 多态可以提高代码扩展性。
- 不同类的方法名要统一。
- 同名方法的参数最好统一。
- 同名方法的返回值含义最好统一。
- 抽象基类可以规定子类必须实现的方法。
- 不要为了多态强行设计复杂类。
常用代码模板:
class A:
def do(self):
print("A 的实现")
class B:
def do(self):
print("B 的实现")
def run(obj):
obj.do()
run(A())
run(B())
继承多态模板:
class Base:
def do(self):
raise NotImplementedError("子类必须实现 do 方法")
class ChildA(Base):
def do(self):
print("ChildA 的实现")
class ChildB(Base):
def do(self):
print("ChildB 的实现")
def run(obj):
obj.do()
课堂记忆口诀:
同名方法,不同表现。
统一调用,各自实现。
少写判断,多靠对象。
参数统一,返回统一。
新增类型,少改旧码。
最后记住:
多态的重点不是写很多类,
而是让不同对象用统一方式被调用,
并让每个对象自己负责自己的具体实现。