欢迎来到 Python 学习计划的第 41 天!🎉
昨天我们深入探讨了 多重继承与 MRO,解决了复杂继承结构中的方法调用顺序问题。今天,我们将进入 OOP 三大特征的最后一站——多态(Polymorphism)。
多态是面向对象编程最强大的特性之一,它让代码更灵活、更易扩展。配合 Python 独特的 鸭子类型(Duck Typing) 理念,你将写出更"Pythonic"的代码!
一、什么是多态(Polymorphism)?
1. 定义
多态 指的是 同一接口,不同实现。即不同的对象可以对同一消息做出不同的响应。
2. 核心思想
- 接口统一:调用者无需关心对象的具体类型,只需知道对象拥有某个方法。
- 行为多样:不同类型的对象执行该方法时,表现出不同的行为。
3. 示例:动物叫声
class Dog: def speak(self): return "汪汪汪"class Cat: def speak(self): return "喵喵喵"class Duck: def speak(self): return "嘎嘎嘎"# 统一接口函数def make_it_speak(animal): print(animal.speak())# 传入不同对象,表现不同行为make_it_speak(Dog()) # 汪汪汪make_it_speak(Cat()) # 喵喵喵make_it_speak(Duck()) # 嘎嘎嘎
💡 关键点:make_it_speak 函数不需要知道传入的是 Dog 还是 Cat,只要对象有 speak() 方法即可。二、鸭子类型(Duck Typing)
1. 什么是鸭子类型?
这是 Python 中多态的典型体现。名言:“如果走起来像鸭子,叫起来像鸭子,那它就是鸭子。”
- 含义:不关心对象的类型,只关心对象是否有所需的方法或属性。
- 对比:静态语言(如 Java)关心“它是什么类”,Python 关心“它能做什么”。
2. 示例:len() 函数
Python 内置的 len() 函数是鸭子类型的完美案例。
print(len([1, 2, 3])) # 3 (列表)print(len("Hello")) # 5 (字符串)print(len({"a": 1, "b": 2})) # 2 (字典)
💡 原理:len() 不检查对象是否是 list 或 str,只检查对象是否实现了 __len__() 方法。3. 自定义鸭子类型
class MyList: def __init__(self, items): self.items = items def __len__(self): return len(self.items)my_list = MyList([1, 2, 3, 4])print(len(my_list)) # 4 (虽然不是真正的 list,但行为像 list)
三、Python 的动态特性如何支持多态?
1. 动态类型(Dynamic Typing)
Python 在运行时才确定变量类型,不需要显式声明类型。这使得函数可以接收任何类型的对象。
# 不需要声明 animal 是 Animal 类型def make_it_speak(animal): animal.speak()
2. 隐式接口
不需要像 Java 那样定义 interface 或 abstract class(虽然 Python 也有 ABC,见下文)。只要对象有该方法,就能调用。
3. 灵活性 vs 安全性
- 优势:代码简洁,扩展性强(新增类无需修改调用代码)。
- 劣势:运行时才报错(如果对象没有该方法)。
class Stone: pass# 运行时才会报错# make_it_speak(Stone()) # AttributeError: 'Stone' object has no attribute 'speak'
四、抽象基类(ABC)的作用
虽然鸭子类型很灵活,但有时我们需要显式约束子类必须实现某些方法。这时就需要 抽象基类(Abstract Base Class, ABC)。
1. 为什么需要 ABC?
- 强制规范:确保子类实现了特定方法。
- 文档说明:明确告知开发者子类应该具备哪些功能。
- 类型检查:配合
isinstance() 使用,提供一定的类型安全感。
2. 如何使用 ABC?
需要导入 abc 模块,使用 ABC 基类和 @abstractmethod 装饰器。
from abc import ABC, abstractmethod# 定义抽象基类class Payment(ABC): @abstractmethod def pay(self, amount): """必须实现支付方法""" pass# 子类必须实现 pay 方法class Alipay(Payment): def pay(self, amount): print(f"使用支付宝支付 {amount} 元")class WeChatPay(Payment): def pay(self, amount): print(f"使用微信支付 {amount} 元")# 错误示例:未实现抽象方法# class Cash(Payment):# pass # ❌ 实例化时会报错alipay = Alipay()alipay.pay(100) # 使用支付宝支付 100 元
💡 最佳实践:在大型项目中,建议使用 ABC 定义核心接口,避免鸭子类型过于松散导致的维护困难。五、多态在实际开发中的应用
1. 支付系统(策略模式)
不同支付方式统一接口,方便切换。
def process_order(payment_method, amount): # 不关心具体是哪种支付,只要有 pay 方法 payment_method.pay(amount)process_order(Alipay(), 100)process_order(WeChatPay(), 100)
2. 文件处理器
统一处理不同格式的文件。
class FileReader: def read(self): passclass TxtReader(FileReader): def read(self): return "读取 TXT 内容"class CsvReader(FileReader): def read(self): return "读取 CSV 内容"def load_data(reader): print(reader.read())load_data(TxtReader())load_data(CsvReader())
3. 日志记录器
支持输出到控制台、文件或数据库。
class Logger: def log(self, message): passclass ConsoleLogger(Logger): def log(self, message): print(f"[Console] {message}")class FileLogger(Logger): def log(self, message): with open("log.txt", "a") as f: f.write(message)
六、常见误区与注意事项
1. 过度依赖鸭子类型
如果对象太多且结构复杂,完全依赖鸭子类型会导致代码难以理解。大型项目建议使用 ABC 或类型注解。
2. 混淆继承与多态
- 继承 是实现多态的一种方式,但不是唯一方式。
- 鸭子类型不需要继承,只要方法名相同即可。
3. 运行时错误
鸭子类型在运行时才检查方法是否存在。建议配合单元测试或类型检查工具(如 mypy)使用。
4. 命名一致性
确保不同类的同名方法功能相似。不要让 Dog.speak() 返回数字,而 Cat.speak() 返回字符串。
七、实战练习
练习 1:图形面积计算
创建一个抽象基类 Shape,要求子类实现 area() 方法。创建 Circle 和 Rectangle 子类,计算总面积。
from abc import ABC, abstractmethodimport mathclass Shape(ABC): @abstractmethod def area(self): passclass Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return math.pi * self.radius ** 2class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.heightshapes = [Circle(5), Rectangle(4, 6)]total_area = sum(shape.area() for shape in shapes)print(f"总面积:{total_area:.2f}")
练习 2:实现鸭子类型
编写一个函数 print_length(obj),可以打印任何实现了 __len__() 方法的对象的长度(列表、字符串、自定义类)。
class MyContainer: def __init__(self, items): self.items = items def __len__(self): return len(self.items)def print_length(obj): # 鸭子类型:不检查类型,直接调用 len() print(f"长度:{len(obj)}")print_length([1, 2, 3]) # 长度:3print_length("Hello") # 长度:5print_length(MyContainer([1, 2])) # 长度:2
八、总结
知识点 | 说明 |
|---|
多态 | 同一接口,不同实现。提高代码扩展性 |
鸭子类型 | 不关心类型,只关心行为(方法/属性) |
动态支持 | Python 动态类型允许函数接收任意对象 |
抽象基类 (ABC) | 强制子类实现特定方法,提供规范约束 |
应用场景 | 支付系统、文件处理、日志记录等 |
注意事项 | 大型项目建议结合 ABC 与类型注解 |
📌 明日预告:OOP 三大特征之封装
明天我们将进入 OOP 核心阶段最后一天!
- 主题:封装(Encapsulation)与私有属性
- 核心问题:
- 什么是封装?为什么要隐藏内部实现?
- 私有属性(
__var)真的私有吗? - 如何安全地访问私有属性?(Getter/Setter)
@property 装饰器的高级用法?- 名称修饰(Name Mangling)机制是什么?
💡 提前思考:如果有一个 BankAccount 类,我们应该允许用户直接修改 balance 属性吗?如何保护它?
掌握多态,让你的代码更灵活、更易扩展!继续加油!🚀