当前位置:首页>python>【一起学 Python】第41天:

【一起学 Python】第41天:

  • 2026-09-10 00:59:20
【一起学 Python】第41天:

欢迎来到 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)与私有属性
  • 核心问题:
    1. 什么是封装?为什么要隐藏内部实现?
    2. 私有属性(__var)真的私有吗?
    3. 如何安全地访问私有属性?(Getter/Setter)
    4. @property 装饰器的高级用法?
    5. 名称修饰(Name Mangling)机制是什么?

💡 提前思考:如果有一个 BankAccount 类,我们应该允许用户直接修改 balance 属性吗?如何保护它?

掌握多态,让你的代码更灵活、更易扩展!继续加油!🚀

最新文章

随机文章