Python教程33:多态

“一个接口,多种实现。”

多态是面向对象编程三大特性(封装、继承、多态)的最后一个。今天我们学习Python的多态特性,理解如何让代码更灵活、更易扩展。

1. 什么是多态

多态(Polymorphism)

  • Poly(多个)+ morph(形态)
  • 同一个接口,不同的实现
  • 不同对象对同一消息的不同响应

为什么需要多态

  • 灵活性:通过父类引用调用子类方法
  • 可扩展:添加新类无需修改现有代码
  • 统一接口:不同对象用相同方式操作
  • 解耦合:调用者不关心对象类型

简单示例

 1class Animal:
 2    """动物基类"""
 3    def speak(self):
 4        pass
 5
 6class Dog(Animal):
 7    def speak(self):
 8        return "Woof!"
 9
10class Cat(Animal):
11    def speak(self):
12        return "Meow!"
13
14class Cow(Animal):
15    def speak(self):
16        return "Moo!"
17
18# 多态:相同的方法调用,不同的行为
19def animal_sound(animal):
20    """
21    统一的接口
22    - 参数是Animal类型
23    - 不关心具体是什么动物
24    - 调用speak()得到不同的结果
25    """
26    print(animal.speak())
27
28# 使用
29animals = [Dog(), Cat(), Cow()]
30for animal in animals:
31    animal_sound(animal)
32# 输出:
33# Woof!
34# Meow!
35# Moo!

多态的好处

  • animal_sound()不需要知道传入的是什么动物
  • 添加新动物类(如Pig)无需修改animal_sound()
  • 代码更灵活、更易扩展

2. Python的动态类型多态

Python是动态类型语言,多态更自然:

 1#Python:不需要显式继承
 2class Dog:
 3    def speak(self):
 4        return "Woof!"
 5
 6class Cat:
 7    def speak(self):
 8        return "Meow!"
 9
10class Robot:
11    def speak(self):
12        return "Beep boop!"
13
14def make_speak(obj):
15    """
16    只要对象有speak()方法就行
17    - 不要求继承关系
18    - 不要求特定类型
19    - 这就是"鸭子类型"
20    """
21    print(obj.speak())
22
23# 所有有speak()方法的对象都可以传入
24make_speak(Dog())
25make_speak(Cat())
26make_speak(Robot())  # Robot不是Animal,但也能用

Python vs 静态类型语言

Java需要显式继承和类型声明:

1// void makeSpeak(Animal animal) {
2//     animal.speak();
3// }
1# Python:只要有对应方法即可
2def make_speak(obj):
3    obj.speak()

3. 鸭子类型(Duck Typing)

鸭子类型

“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”

  • 关注对象的行为,而不是类型
  • 只要有需要的方法/属性,就可以使用
  • Python的核心特性
 1class Duck:
 2    def swim(self):
 3        print("Duck is swimming")
 4    
 5    def fly(self):
 6        print("Duck is flying")
 7
 8class Penguin:
 9    def swim(self):
10        print("Penguin is swimming")
11    
12    # 企鹅不会飞,没有fly()方法
13
14class Airplane:
15    def fly(self):
16        print("Airplane is flying")
17
18def let_it_swim(obj):
19    """只要有swim()方法"""
20    obj.swim()
21
22def let_it_fly(obj):
23    """只要有fly()方法"""
24    obj.fly()
25
26# 使用
27duck = Duck()
28penguin = Penguin()
29plane = Airplane()
30
31let_it_swim(duck)     # OK
32let_it_swim(penguin)  # OK
33# let_it_swim(plane)  # AttributeError: Airplane没有swim()
34
35let_it_fly(duck)  # OK
36let_it_fly(plane) # OK(飞机也能飞!)
37# let_it_fly(penguin)  # AttributeError

鸭子类型的优势

  • 更灵活
  • 不强制继承
  • 易于测试(Mock对象)

鸭子类型的风险

  • 运行时才发现错误
  • 需要明确文档说明接口

4. 魔术方法实现多态

Python的魔术方法让多态更强大:

 1class Vector:
 2    """向量类"""
 3    def __init__(self, x, y):
 4        self.x = x
 5        self.y = y
 6    
 7    def __add__(self, other):
 8        """
 9        重载+运算符
10        - 实现多态:不同类型有不同的+行为
11        """
12        return Vector(self.x + other.x, self.y + other.y)
13    
14    def __str__(self):
15        return f"Vector({self.x}, {self.y})"
16
17v1 = Vector(1, 2)
18v2 = Vector(3, 4)
19v3 = v1 + v2  # 调用__add__
20print(v3)     # Vector(4, 6)
21
22# Python内置类型也有多态
23print(1 + 2)       # 整数加法:3
24print("a" + "b")   # 字符串连接:"ab"
25print([1] + [2])   # 列表连接:[1, 2]

5. 抽象基类(ABC)

使用ABC定义接口规范:

 1from abc import ABC, abstractmethod
 2
 3class Payment(ABC):
 4    """
 5    支付抽象基类
 6    - 定义支付接口规范
 7    - 子类必须实现所有抽象方法
 8    """
 9    
10    @abstractmethod
11    def pay(self, amount):
12        """支付方法(子类必须实现)"""
13        pass
14    
15    @abstractmethod
16    def refund(self, amount):
17        """退款方法(子类必须实现)"""
18        pass
19
20class CreditCardPayment(Payment):
21    """信用卡支付"""
22    def pay(self, amount):
23        return f"Paid ${amount} with credit card"
24    
25    def refund(self, amount):
26        return f"Refunded ${amount} to credit card"
27
28class PayPal(Payment):
29    """PayPal支付"""
30    def pay(self, amount):
31        return f"Paid ${amount} via PayPal"
32    
33    def refund(self, amount):
34        return f"Refunded ${amount} via PayPal"
35
36def process_payment(payment: Payment, amount):
37    """
38    处理支付
39    - 接受Payment类型
40    - 不关心具体支付方式
41    - 多态:不同支付方式有不同实现
42    """
43    print(payment.pay(amount))
44
45# 使用
46credit_card = CreditCardPayment()
47paypal = PayPal()
48
49process_payment(credit_card, 100)
50process_payment(paypal, 50)
51
52# payment = Payment()  # TypeError: 不能实例化抽象类

6. 实战示例:图形绘制系统

 1from abc import ABC, abstractmethod
 2import math
 3
 4class Shape(ABC):
 5    """图形抽象基类"""
 6    
 7    @abstractmethod
 8    def area(self):
 9        """计算面积"""
10        pass
11    
12    @abstractmethod
13    def perimeter(self):
14        """计算周长"""
15        pass
16    
17    def describe(self):
18        """描述图形"""
19        return f"{self.__class__.__name__}: Area={self.area():.2f}, Perimeter={self.perimeter():.2f}"
20
21class Circle(Shape):
22    """圆形"""
23    def __init__(self, radius):
24        self.radius = radius
25    
26    def area(self):
27        return math.pi * self.radius ** 2
28    
29    def perimeter(self):
30        return 2 * math.pi * self.radius
31
32class Rectangle(Shape):
33    """矩形"""
34    def __init__(self, width, height):
35        self.width = width
36        self.height = height
37    
38    def area(self):
39        return self.width * self.height
40    
41    def perimeter(self):
42        return 2 * (self.width + self.height)
43
44class Triangle(Shape):
45    """三角形"""
46    def __init__(self, a, b, c):
47        self.a = a
48        self.b = b
49        self.c = c
50    
51    def area(self):
52        # 海伦公式
53        s = self.perimeter() / 2
54        return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
55    
56    def perimeter(self):
57        return self.a + self.b + self.c
58
59def print_shape_info(shape: Shape):
60    """
61    打印图形信息
62    - 统一接口
63    - 多态:不同图形有不同的计算方式
64    """
65    print(shape.describe())
66
67# 使用
68shapes = [
69    Circle(5),
70    Rectangle(4, 6),
71    Triangle(3, 4, 5)
72]
73
74for shape in shapes:
75    print_shape_info(shape)
76# 输出:
77# Circle: Area=78.54, Perimeter=31.42
78# Rectangle: Area=24.00, Perimeter=20.00
79# Triangle: Area=6.00, Perimeter=12.00

7. 多态的设计模式

策略模式

 1from abc import ABC, abstractmethod
 2
 3class SortStrategy(ABC):
 4    """排序策略抽象类"""
 5    @abstractmethod
 6    def sort(self, data):
 7        pass
 8
 9class BubbleSort(SortStrategy):
10    """冒泡排序"""
11    def sort(self, data):
12        print("Using bubble sort")
13        return sorted(data)  # 简化示例
14
15class QuickSort(SortStrategy):
16    """快速排序"""
17    def sort(self, data):
18        print("Using quick sort")
19        return sorted(data)  # 简化示例
20
21class Sorter:
22    """排序器"""
23    def __init__(self, strategy: SortStrategy):
24        self.strategy = strategy
25    
26    def sort(self, data):
27        """使用策略排序"""
28        return self.strategy.sort(data)
29
30# 使用
31data = [3, 1, 4, 1, 5, 9, 2, 6]
32
33sorter = Sorter(BubbleSort())
34print(sorter.sort(data))
35
36# 切换策略
37sorter.strategy = QuickSort()
38print(sorter.sort(data))

8. 类型检查与多态

虽然Python支持鸭子类型,但有时需要类型检查:

 1def process(obj):
 2    """处理对象"""
 3    # 方式1:鸭子类型(推荐)
 4    try:
 5        result = obj.process()
 6        return result
 7    except AttributeError:
 8        return "Object doesn't support processing"
 9    
10    # 方式2:类型检查(不推荐)
11    if isinstance(obj, Processor):
12        return obj.process()
13    
14    # 方式3:hasattr检查
15    if hasattr(obj, 'process'):
16        return obj.process()

最佳实践

  • 优先使用鸭子类型
  • 必要时使用抽象基类
  • 避免过度类型检查

9. 与Golang对比

Golang使用接口实现多态:

 1// Go:显式接口
 2type Speaker interface {
 3    Speak() string
 4}
 5
 6type Dog struct{}
 7func (d Dog) Speak() string {
 8    return "Woof!"
 9}
10
11type Cat struct{}
12func (c Cat) Speak() string {
13    return "Meow!"
14}
15
16// 函数接受接口类型
17func MakeSpeak(s Speaker) {
18    fmt.Println(s.Speak())
19}
20
21// 使用
22MakeSpeak(Dog{})
23MakeSpeak(Cat{})

对比

  • Python:鸭子类型,隐式接口
  • Go:显式接口,编译时检查
  • Python:更灵活,运行时错误
  • Go:更安全,编译时错误

10. 小结

今天我们学习了多态:

  • 定义:同一接口,多种实现
  • Python多态:动态类型,鸭子类型
  • 鸭子类型:关注行为而非类型
  • 抽象基类:定义接口规范
  • 实战应用:图形系统、策略模式
  • 最佳实践:优先鸭子类型,必要时ABC

多态让代码更灵活、更易扩展,是面向对象编程的核心特性。


练习题

  1. 实现一个文件处理系统,支持txt、csv、json等多种格式(多态)
  2. 用策略模式实现计算器的不同运算策略
  3. 设计一个通知系统,支持邮件、短信、推送等多种方式

本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读