Day 27 - 多态与鸭子类型

什么是多态?

多态(Polymorphism)是面向对象编程的三大基本特性之一,意思是"多种形态"。在 Python 中,多态指的是同一种操作(如调用同一个方法)在不同类型的对象上会产生不同的行为。

多态的核心思想是:关注对象的"行为"而非"类型"。当我们调用一个对象的方法时,不需要关心这个对象是什么类型的具体类,只需要知道它支持这个方法即可。这大大增强了代码的灵活性和可扩展性。

class Dog:
    def speak(self):
        return "汪汪汪"

class Cat:
    def speak(self):
        return "喵喵喵"

class Duck:
    def speak(self):
        return "嘎嘎嘎"

# 多态:同一个方法调用,不同对象产生不同行为
animals = [Dog(), Cat(), Duck()]

for animal in animals:
    print(f"{animal.__class__.__name__} 说:{animal.speak()}")

# 输出:
# Dog 说:汪汪汪
# Cat 说:喵喵喵
# Duck 说:嘎嘎嘎

Python 中的多态实现

Python 是动态类型语言,天生支持多态。在静态类型语言(如 Java、C++)中,多态需要通过继承和接口来实现。但在 Python 中,任何定义了 speak() 方法的对象都可以被调用,而不需要继承关系。

class People:
    def speak(self):
        return "你好!"

class Bird:
    def speak(self):
        return "叽叽喳喳"

def make_speak(obj):
    """多态函数:接收任何有 speak 方法的对象"""
    print(obj.speak())

person = People()
bird = Bird()

make_speak(person)  # 你好!
make_speak(bird)    # 叽叽喳喳

抽象基类与多态

虽然 Python 不强制使用抽象基类,但抽象基类(ABC)提供了一种方式来定义接口规范,确保子类实现了必要的方法。这在大型项目中很有用。

from abc import ABC, abstractmethod

class Shape(ABC):
    """抽象基类:形状"""
    
    @abstractmethod
    def area(self):
        """计算面积"""
        pass
    
    @abstractmethod
    def perimeter(self):
        """计算周长"""
        pass
    
    def describe(self):
        """通用描述方法"""
        return f"面积:{self.area():.2f},周长:{self.perimeter():.2f}"

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

# 多态使用
shapes = [Rectangle(5, 3), Circle(5), Rectangle(10, 2), Circle(3)]

total_area = sum(shape.area() for shape in shapes)
total_perimeter = sum(shape.perimeter() for shape in shapes)

print(f"总面积:{total_area:.2f}")
print(f"总周长:{total_perimeter:.2f}")

for shape in shapes:
    print(f"{shape.__class__.__name__}: {shape.describe()}")

鸭子类型(Duck Typing)

“Duck Typing” 这个术语来源于一句话:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子”(If it walks like a duck and quacks like a duck, it’s a duck)。

在 Python 中,鸭子类型是一种编程范式,它关注对象的方法和属性,而非对象的类型。只要对象具有所需的方法和属性,它就可以被使用,而不需要显式的继承或实现某个接口。

class Duck:
    def swim(self):
        print("鸭子游泳")
    
    def fly(self):
        print("鸭子飞翔")

class Person:
    def swim(self):
        print("人游泳")
    
    def fly(self):
        print("人不能飞,但人会坐飞机")

# 函数只要求对象有 swim 和 fly 方法
def make_it_do_things(obj):
    obj.swim()
    obj.fly()

duck = Duck()
person = Person()

print("Duck:")
make_it_do_things(duck)
# 鸭子游泳
# 鸭子飞翔

print("\nPerson:")
make_it_do_things(person)
# 人游泳
# 人不能飞,但人会坐飞机

鸭子类型的优势

鸭子类型的优势在于它的灵活性和松耦合性。不需要显式的继承关系,使得代码更加灵活。

class TextFile:
    def read(self):
        return "文本文件内容"

class CSVFile:
    def read(self):
        return "CSV文件内容"

class APIClient:
    def read(self):
        return "API返回的数据"

class Database:
    def read(self):
        return "数据库查询结果"

# 所有这些类都可以被同一个函数使用
def process_data(source):
    data = source.read()
    print(f"处理数据:{data}")

# 不需要继承共同的基类,只要实现了 read 方法就行
process_data(TextFile())
process_data(CSVFile())
process_data(APIClient())
process_data(Database())

协议(Protocols)与结构子类型

Python 3.8 引入了 typing.Protocol,用于定义结构子类型(Structural Subtyping)。这提供了一种方式来明确指定接口,而不需要显式的继承。

from typing import Protocol

class Readable(Protocol):
    """可读协议"""
    def read(self) -> str:
        ...

class Writeable(Protocol):
    """可写协议"""
    def write(self, data: str) -> None:
        ...

class File:
    def read(self) -> str:
        return "文件内容"
    
    def write(self, data: str) -> None:
        print(f"写入数据:{data}")

class Console:
    def read(self) -> str:
        return "控制台输入"
    
    def write(self, data: str) -> None:
        print(data)

def process_file(file: Readable) -> None:
    print(f"读取数据:{file.read()}")

def save_file(file: Writeable, data: str) -> None:
    file.write(data)

# File 和 Console 都实现了 Readable 和 Writeable 协议
file = File()
console = Console()

process_file(file)    # OK
process_file(console)  # OK
save_file(file, "新数据")
save_file(console, "控制台输出")

双分派(Double Dispatch)

双分派是一种多态技术,允许根据一个方法的多个参数类型来选择不同的实现。Python 本身不直接支持双分派,但可以通过一些技巧来实现。

class Node:
    def accept(self, visitor):
        """接受访问者"""
        return visitor.visit(self)

class TextNode(Node):
    def accept(self, visitor):
        return visitor.visit_text(self)
    
    def get_text(self):
        return "文本内容"

class ImageNode(Node):
    def accept(self, visitor):
        return visitor.visit_image(self)
    
    def get_url(self):
        return "http://example.com/image.jpg"

class Visitor:
    def visit(self, node):
        raise NotImplementedError
    
    def visit_text(self, node):
        return f"文本:{node.get_text()}"
    
    def visit_image(self, node):
        return f"图片:{node.get_url()}"

# 使用
nodes = [TextNode(), ImageNode()]
visitor = Visitor()

for node in nodes:
    print(node.accept(visitor))
# 文本:文本内容
# 图片:http://example.com/image.jpg

多态的实际应用

示例 1:插件系统

class Plugin:
    """插件基类"""
    def process(self, data):
        raise NotImplementedError

class UpperCasePlugin(Plugin):
    def process(self, data):
        return data.upper()

class ReversePlugin(Plugin):
    def process(self, data):
        return data[::-1]

class AddPrefixPlugin(Plugin):
    def __init__(self, prefix):
        self.prefix = prefix
    
    def process(self, data):
        return f"{self.prefix}{data}"

# 插件系统
class PluginSystem:
    def __init__(self):
        self.plugins = []
    
    def register(self, plugin):
        self.plugins.append(plugin)
    
    def process(self, data):
        result = data
        for plugin in self.plugins:
            result = plugin.process(result)
        return result

# 使用
system = PluginSystem()
system.register(AddPrefixPlugin(">>> "))
system.register(UpperCasePlugin())
system.register(ReversePlugin())

result = system.process("hello")
print(result)  # >>> OLLEH

示例 2:序列化系统

class Serializer:
    """序列化器基类"""
    def serialize(self, obj):
        raise NotImplementedError

class JSONSerializer(Serializer):
    def serialize(self, obj):
        import json
        return json.dumps(obj, ensure_ascii=False)

class XMLSerializer(Serializer):
    def serialize(self, obj):
        items = ''.join(f'<item key="{k}">{v}</item>' for k, v in obj.items())
        return f'<dict>{items}</dict>'

class YAMLSerializer(Serializer):
    def serialize(self, obj):
        import yaml
        return yaml.dump(obj)

def export_data(data, serializer: Serializer):
    """导出数据,使用任意序列化器"""
    return serializer.serialize(data)

data = {"name": "张三", "age": 25, "city": "北京"}

print("JSON:", export_data(data, JSONSerializer()))
print("XML:", export_data(data, XMLSerializer()))
print("YAML:", export_data(data, YAMLSerializer()))

方法重写与方法重载

方法重写(Override):子类重新定义父类的方法,改变行为。

方法重载(Overload):同一个方法名,根据参数类型或个数执行不同逻辑。Python 默认不支持方法重载(同一个类中不能有两个同名方法),但可以通过默认参数或类型检查实现类似功能。

class Math:
    def calculate(self, a, b=0, c=0):
        """使用默认参数实现类似重载的功能"""
        if b == 0 and c == 0:
            return a
        elif c == 0:
            return a + b
        else:
            return a + b + c

m = Math()
print(m.calculate(5))      # 5
print(m.calculate(5, 3))  # 8
print(m.calculate(5, 3, 2))  # 10

多态与设计模式

多态是许多设计模式的基础:

策略模式

class SortStrategy:
    def sort(self, data):
        raise NotImplementedError

class BubbleSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        n = len(arr)
        for i in range(n):
            for j in range(0, n-i-1):
                if arr[j] > arr[j+1]:
                    arr[j], arr[j+1] = arr[j+1], arr[j]
        return arr

class QuickSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        arr.sort()
        return arr

class Sorter:
    def __init__(self, strategy):
        self.strategy = strategy
    
    def set_strategy(self, strategy):
        self.strategy = strategy
    
    def sort(self, data):
        return self.strategy.sort(data)

# 使用
sorter = Sorter(BubbleSort())
print(sorter.sort([3, 1, 4, 1, 5, 9, 2, 6]))

sorter.set_strategy(QuickSort())
print(sorter.sort([3, 1, 4, 1, 5, 9, 2, 6]))

练习题

练习 1:实现银行账户的多态处理

class Account:
    def __init__(self, balance):
        self.balance = balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("余额不足")
        self.balance -= amount

class SavingsAccount(Account):
    """储蓄账户:有利息"""
    def __init__(self, balance, interest_rate):
        super().__init__(balance)
        self.interest_rate = interest_rate
    
    def add_interest(self):
        interest = self.balance * self.interest_rate
        self.balance += interest
        return interest

class CheckingAccount(Account):
    """支票账户:有透支额度"""
    def __init__(self, balance, overdraft_limit):
        super().__init__(balance)
        self.overdraft_limit = overdraft_limit
    
    def withdraw(self, amount):
        if amount > self.balance + self.overdraft_limit:
            raise ValueError("超过透支额度")
        self.balance -= amount

def process_account(account: Account, withdraw_amount):
    """处理账户提款(多态)"""
    try:
        account.withdraw(withdraw_amount)
        print(f"成功取款 {withdraw_amount},余额:{account.balance}")
    except ValueError as e:
        print(f"取款失败:{e}")

# 测试
savings = SavingsAccount(1000, 0.03)
checking = CheckingAccount(1000, 500)

process_account(savings, 200)    # 余额不足
# 等等,这个设计有问题...让我们修正

savings.deposit = lambda x: setattr(savings, 'balance', savings.balance + x) if hasattr(savings, 'deposit') else None

练习 2:图形编辑器

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def draw(self):
        pass
    
    @abstractmethod
    def move(self, dx, dy):
        pass

class Circle(Shape):
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    
    def draw(self):
        print(f"绘制圆形:圆心({self.x},{self.y}),半径{self.radius}")
    
    def move(self, dx, dy):
        self.x += dx
        self.y += dy
        print(f"移动圆形到:({self.x},{self.y})")

class Rectangle(Shape):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
    
    def draw(self):
        print(f"绘制矩形:左上角({self.x},{self.y}),宽{self.width},高{self.height}")
    
    def move(self, dx, dy):
        self.x += dx
        self.y += dy
        print(f"移动矩形到:({self.x},{self.y})")

class Canvas:
    def __init__(self):
        self.shapes = []
    
    def add_shape(self, shape: Shape):
        self.shapes.append(shape)
    
    def draw_all(self):
        for shape in self.shapes:
            shape.draw()
    
    def move_all(self, dx, dy):
        for shape in self.shapes:
            shape.move(dx, dy)

# 测试
canvas = Canvas()
canvas.add_shape(Circle(0, 0, 5))
canvas.add_shape(Rectangle(10, 10, 20, 15))

canvas.draw_all()
# 绘制圆形:圆心(0,0),半径5
# 绘制矩形:左上角(10,10),宽20,高15

print()
canvas.move_all(5, 5)
# 移动圆形到:(5,5)
# 移动矩形到:(15,15)

练习 3:通知系统

from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, message):
        pass

class EmailNotifier(Notifier):
    def __init__(self, email):
        self.email = email
    
    def send(self, message):
        print(f"发送邮件到 {self.email}{message}")

class SMSNotifier(Notifier):
    def __init__(self, phone):
        self.phone = phone
    
    def send(self, message):
        print(f"发送短信到 {self.phone}{message}")

class WeChatNotifier(Notifier):
    def __init__(self, openid):
        self.openid = openid
    
    def send(self, message):
        print(f"发送微信到 {self.openid}{message}")

class NotificationService:
    def __init__(self):
        self.notifiers = []
    
    def add_notifier(self, notifier: Notifier):
        self.notifiers.append(notifier)
    
    def notify(self, message):
        for notifier in self.notifiers:
            notifier.send(message)

# 测试
service = NotificationService()
service.add_notifier(EmailNotifier("user@example.com"))
service.add_notifier(SMSNotifier("13800138000"))
service.add_notifier(WeChatNotifier("wx_openid_123"))

service.notify("您的订单已发货!")
# 发送邮件到 user@example.com:您的订单已发货!
# 发送短信到 13800138000:您的订单已发货!
# 发送微信到 wx_openid_123:您的订单已发货!

总结

多态和鸭子类型是 Python 面向对象编程的核心概念:

  1. 多态:同一种操作在不同对象上产生不同行为
  2. 鸭子类型:关注对象的行为而非类型
  3. 抽象基类:通过 @abstractmethod 定义接口规范
  4. Protocol:Python 3.8+ 的结构子类型支持
  5. 设计模式:多态是策略模式、访问者模式等的基础

Python 的多态是动态的、运行时决定的,这使得代码更加灵活。在下一节中,我们将学习 @classmethod 装饰器。