多态
约 1714 字大约 6 分钟
2026-01-22
阅读导览
多态
多态(Polymorphism)是面向对象编程(OOP)的三大基本特征之一(另外两个是封装和继承)。它的核心思想是“一种接口,多种实现”。
- 基本概念
- 基本语法
- 业务示例
- 注意事项
实践清单
学习练习清单
- 1写一个吃 animal.speak() 的函数,分别传 Dog、Cat、Robot —— Robot 不继承 Animal 也能跑,体会鸭子类型。
- 2把 if pay_type == 'wechat': ... elif 'alipay': ... 改成多态:每种支付一个类,都实现 pay() —— 这是“开闭原则”的典型应用。
- 3Python 没有方法重载,用 functools.singledispatch 写一个按类型分派的函数体会替代方案。
- 4在多态函数签名上加 processor: PaymentProcessor 类型提示,让 IDE/mypy 帮你提前发现“传错对象”。
- 5不要为多态而多态:只有两个分支且未来不会扩,老老实实写 if/else 比定义一堆类清楚。
多态(Polymorphism)是面向对象编程(OOP)的三大基本特征之一(另外两个是封装和继承)。它的核心思想是“一种接口,多种实现”。
在 Python 中,多态的体现非常自然,主要依赖于 继承 和 鸭子类型(Duck Typing)。简单来说,多态允许我们使用统一的接口来操作不同类型的对象,而不需要知道对象的具体类型。
基本概念
什么是多态?
字面意思就是“多种形态”。在编程中,它指的是同一个方法调用,在不同的对象上会产生不同的行为。
例如,+ 操作符就是一个多态的例子:
1 + 2得到3(整数相加)- 'Hello' + ' ' + 'World' 得到 'Hello World' (字符串拼接)
[1, 2] + [3, 4]得到[1, 2, 3, 4](列表合并)
同一个符号 +,作用于不同对象时,表现出了不同的行为。
静态多态 vs 动态多态
- 静态多态(编译时多态):通常指方法重载(Overloading),即在这个类中定义多个同名方法,但参数列表不同。Java 和 C++ 支持这种特性。Python 原生不支持方法重载(后定义的同名方法会覆盖前面的),但可以使用
functools.singledispatch或默认参数模拟。 - 动态多态(运行时多态):通常指方法重写(Overriding),即子类重写父类的方法。程序在运行时根据对象的实际类型来决定调用哪个方法。这是 Python 多态的主要形式。
鸭子类型 (Duck Typing)
Python 推崇“鸭子类型”:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”
在 Python 中,我们通常不关心对象是什么类型,只关心对象是否有我们需要的方法或属性。这意味着两个类不一定要继承自同一个父类,只要它们实现了相同的方法,就可以在同一个多态函数中使用。
基本语法
1. 基于继承的多态
这是最标准的多态形式。子类继承父类,并重写父类的方法。
class Animal:
def speak(self):
print('Animal speaks')
class Dog(Animal):
def speak(self):
print('Woof!')
class Cat(Animal):
def speak(self):
print('Meow!')
def animal_sound(animal):
# 这里 animal 可以是 Dog 或 Cat,甚至是 Animal
# 具体的行为取决于传入对象的类型
animal.speak()
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!2. 基于鸭子类型的多态
注意下面的 Car 类并没有继承 Animal,但它也有 speak 方法,所以它也能被 animal_sound 调用。
class Car:
def speak(self):
print('Honk! Honk!')
car = Car()
animal_sound(car) # 输出: Honk! Honk!这就是 Python 的灵活之处:不关注身份(类型),只关注行为(方法)。
业务示例
假设我们正在开发一个电商平台的支付系统。系统支持多种支付方式:支付宝、微信支付、信用卡。
如果不使用多态,我们可能需要写很多 if-elif 语句来判断支付类型,类似于:
# 糟糕的写法
def pay(payment_type, amount):
if payment_type == 'alipay':
# ... 支付宝逻辑
elif payment_type == 'wechat':
# ... 微信逻辑
# 每次增加新支付方式都要修改这里,违反了“开闭原则”使用多态,我们可以将设计改为:
from abc import ABC, abstractmethod
# 定义一个抽象基类,强制子类实现 pay 方法
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, amount):
pass
class AlipayProcessor(PaymentProcessor):
def pay(self, amount):
print(f'正在使用支付宝支付 ¥{amount} ... 支付成功!')
class WechatPayProcessor(PaymentProcessor):
def pay(self, amount):
print(f'正在使用微信支付 ¥{amount} ... 支付成功!')
class CreditCardProcessor(PaymentProcessor):
def pay(self, amount):
print(f'正在使用信用卡支付 ¥{amount} ... 支付成功!')
# 统一的支付接口
def process_user_order(processor: PaymentProcessor, amount):
"""
业务逻辑层不需要知道具体的支付方式是什么。
只要 processor 有 pay 方法即可。
"""
print('开始处理订单...')
processor.pay(amount)
print('订单处理完毕。\n')
# --- 客户端代码 ---
orders = [
(AlipayProcessor(), 100),
(WechatPayProcessor(), 250),
(CreditCardProcessor(), 99.9)
]
for processor, amount in orders:
process_user_order(processor, amount)优点:
- 可扩展性强:如果以后要支持“银联支付”,只需新建一个
UnionPayProcessor类,不需要修改process_user_order中的任何代码。符合 OCP(开闭原则)。 - 解耦:业务逻辑层与具体的支付实现层解耦。
注意事项
- 运行时错误:由于 Python 是动态语言,如果不使用
ABC(抽象基类) 强制约束接口,且传入的对象没有预期的那个方法(例如把没有pay方法的对象传给了process_user_order),程序会在运行时抛出AttributeError。- 建议:对于关键业务接口,建议使用
abc模块定义抽象基类,或者在代码中增加适当的hasattr检查或异常处理。
- 建议:对于关键业务接口,建议使用
- 类型提示 (Type Hints):虽然 Python 不强制类型,但强烈建议在多态函数中使用类型提示(如上面示例中的
processor: PaymentProcessor),这样通过 IDE 或静态检查工具(如 MyPy)可以提前发现潜在问题,也能增加代码可读性。 - 过度设计:不要为了多态而多态。如果你的逻辑非常简单,只有两种情况且未来不太可能变化,简单的
if-else可能比定义一堆类更清晰直观。
重点总结
要点回收
- 多态 = 一个接口、多种实现:调用方只依赖方法名,不关心对象具体类型。
- Python 里的多态主要靠鸭子类型 + 方法重写;不需要像 Java 那样强行继承同一基类。
- 用多态替代不断膨胀的 if/elif 是“开闭原则”的标准落地方式:新增分支只加新类,不动旧代码。
- Python 无重载(同名方法后面覆盖前面);按类型分派用 functools.singledispatch。
- 对外部输入或插件接口,加 ABC/Protocol 约束 + 类型提示,比纯鸭子类型更稳。
版权所有
版权归属:Shuo Liu
