python-第29天:Python面向对象之多态与抽象类

30天入门Python(基础篇)------第29天:Python面向对象之多态与抽象类

📅 学习日期 :第29天 | ⏱️ 预计用时 :50分钟 | 📊 难度等级:⭐⭐⭐⭐


🎯 学习目标

  • 理解多态的概念和意义
  • 掌握鸭子类型(Duck Typing)的思想
  • 学会使用 abc 模块创建抽象类
  • 理解抽象方法和具体方法的区别
  • 掌握 @abstractmethod@abstractproperty
  • 了解接口与实现分离的设计思想
  • 能够设计可扩展的面向对象架构

一、什么是多态?

1.1 多态的概念

多态(Polymorphism) 来自希腊语,意思是"多种形态"。在面向对象编程中,多态指的是:

同一个方法调用,可以根据对象的不同,表现出不同的行为。

简单说就是:一种接口,多种实现

1.2 生活类比

  • "吃"这个动作:人用筷子吃,狗用嘴吃,鸟用喙吃。接口都是"吃",但实现方式不同。
  • "支付"功能 :微信支付、支付宝支付、银行卡支付。用户只调用 pay(),底层实现各不相同。

1.3 多态示例

python 复制代码
class Dog:
    def speak(self):
        return "汪汪!"

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

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

# 多态:统一接口,不同实现
def animal_sound(animal):
    print(animal.speak())

animals = [Dog(), Cat(), Duck()]
for animal in animals:
    animal_sound(animal)

# 输出:
# 汪汪!
# 喵喵!
# 嘎嘎!

关键点animal_sound() 函数不需要知道传入的是什么具体类型的对象,只要它有 speak() 方法就行。


二、Python 中的鸭子类型

2.1 什么是鸭子类型?

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

Python 不关心对象的具体类型,只关心它有没有所需的方法和属性。这是 Python 的"动态类型"特性。

python 复制代码
class Car:
    def start(self):
        print("汽车启动")

class Bike:
    def start(self):
        print("自行车启动")

class Boat:
    def start(self):
        print("船启动")

def start_vehicle(vehicle):
    """不需要检查类型,只要有 start 方法就行"""
    vehicle.start()

# 三种不同类型的对象都能使用
start_vehicle(Car())    # 汽车启动
start_vehicle(Bike())   # 自行车启动
start_vehicle(Boat())   # 船启动

2.2 鸭子类型的优势

  • 灵活:不需要继承同一个父类
  • 简洁 :不需要 isinstance() 检查
  • 可扩展:新类只要实现相应方法就能使用

2.3 鸭子类型的陷阱

python 复制代码
class Bird:
    def fly(self):
        print("鸟在飞")

class Plane:
    def fly(self):
        print("飞机在飞")

class Stone:
    pass  # 没有 fly 方法

def make_it_fly(thing):
    thing.fly()

make_it_fly(Bird())   # ✅ 鸟在飞
make_it_fly(Plane())  # ✅ 飞机在飞
# make_it_fly(Stone())  # ❌ AttributeError: 'Stone' object has no attribute 'fly'

解决方案:使用抽象类或协议来约束行为。


三、抽象类

3.1 为什么需要抽象类?

考虑一个支付系统:

python 复制代码
class Payment:
    def pay(self, amount):
        raise NotImplementedError("子类必须实现 pay 方法")

class WeChatPay(Payment):
    def pay(self, amount):
        print(f"微信支付: {amount} 元")

class Alipay(Payment):
    def pay(self, amount):
        print(f"支付宝支付: {amount} 元")

class BankCard(Payment):
    # 忘记实现 pay 方法
    pass

# 运行时才发现错误
payment = BankCard()
payment.pay(100)  # ❌ NotImplementedError

问题:错误在运行时 才被发现。使用抽象类可以在实例化时就报错。

3.2 使用 abc 模块创建抽象类

python 复制代码
from abc import ABC, abstractmethod

class Payment(ABC):
    """支付抽象基类"""

    @abstractmethod
    def pay(self, amount):
        """支付方法,子类必须实现"""
        pass

    def get_payment_info(self):
        """非抽象方法,子类可以直接使用"""
        return f"支付金额: {amount} 元"

class WeChatPay(Payment):
    def pay(self, amount):
        print(f"微信支付: {amount} 元")

class Alipay(Payment):
    def pay(self, amount):
        print(f"支付宝支付: {amount} 元")

class BankCard(Payment):
    pass  # 没有实现 pay 方法

# 测试
wechat = WeChatPay()
wechat.pay(100)  # ✅ 微信支付: 100 元

# alipay = Alipay()
# alipay.pay(200)  # ✅ 支付宝支付: 200 元

# bank_card = BankCard()  # ❌ TypeError: Can't instantiate abstract class BankCard

3.3 抽象类的核心规则

规则 说明
必须继承 ABC 使用 class MyAbstractClass(ABC):
抽象方法用 @abstractmethod 子类必须实现,否则无法实例化
可以有具体方法 抽象类中可以定义非抽象方法
不能直接实例化 AbstractClass() 会报错
子类必须实现所有抽象方法 否则子类也是抽象类

四、抽象类的高级用法

4.1 抽象属性

python 复制代码
from abc import ABC, abstractmethod

class Shape(ABC):
    """图形抽象基类"""

    @property
    @abstractmethod
    def area(self):
        """面积,子类必须实现"""
        pass

    @property
    @abstractmethod
    def perimeter(self):
        """周长,子类必须实现"""
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        import math
        return math.pi * self.radius ** 2

    @property
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

circle = Circle(5)
print(f"面积: {circle.area:.2f}")       # 78.54
print(f"周长: {circle.perimeter:.2f}")  # 31.42

4.2 抽象静态方法和类方法

python 复制代码
from abc import ABC, abstractmethod

class DataSource(ABC):
    @staticmethod
    @abstractmethod
    def get_connection_string():
        """获取连接字符串"""
        pass

    @classmethod
    @abstractmethod
    def from_config(cls, config):
        """从配置创建实例"""
        pass

class MySQLDataSource(DataSource):
    def __init__(self, host, port, db):
        self.host = host
        self.port = port
        self.db = db

    @staticmethod
    def get_connection_string():
        return "mysql+pymysql://..."

    @classmethod
    def from_config(cls, config):
        return cls(config["host"], config["port"], config["db"])

4.3 强制子类实现

python 复制代码
from abc import ABC, abstractmethod

class Logger(ABC):
    @abstractmethod
    def log(self, message):
        """记录日志"""
        pass

    @abstractmethod
    def clear(self):
        """清空日志"""
        pass

    def log_multiple(self, messages):
        """非抽象方法,使用子类实现的 log"""
        for msg in messages:
            self.log(msg)

class FileLogger(Logger):
    def __init__(self, filename):
        self.filename = filename
        self.messages = []

    def log(self, message):
        self.messages.append(message)
        print(f"[FILE] {message}")

    def clear(self):
        self.messages.clear()
        print("日志已清空")

logger = FileLogger("app.log")
logger.log("系统启动")
logger.log("用户登录")
logger.log_multiple(["操作1", "操作2"])
logger.clear()

五、接口与实现分离

5.1 Python 中的"接口"

Python 没有 Java/C# 那样的 interface 关键字,但可以通过只包含抽象方法的抽象类模拟接口:

python 复制代码
from abc import ABC, abstractmethod

class Serializable(ABC):
    """序列化接口"""
    @abstractmethod
    def to_dict(self):
        """转换为字典"""
        pass

    @classmethod
    @abstractmethod
    def from_dict(cls, data):
        """从字典创建对象"""
        pass

class Comparable(ABC):
    """比较接口"""
    @abstractmethod
    def compare_to(self, other):
        """比较两个对象"""
        pass

class Student(Serializable, Comparable):
    """实现多个接口"""
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

    def to_dict(self):
        return {"name": self.name, "age": self.age, "score": self.score}

    @classmethod
    def from_dict(cls, data):
        return cls(data["name"], data["age"], data["score"])

    def compare_to(self, other):
        if self.score > other.score:
            return 1
        elif self.score < other.score:
            return -1
        return 0

# 使用
stu1 = Student("小明", 20, 95)
stu2 = Student("小红", 21, 88)

print(stu1.to_dict())  # {'name': '小明', 'age': 20, 'score': 95}
print(stu1.compare_to(stu2))  # 1(小明成绩更高)

5.2 策略模式(多态的经典应用)

python 复制代码
from abc import ABC, abstractmethod

class SortStrategy(ABC):
    """排序策略接口"""
    @abstractmethod
    def sort(self, data):
        pass

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):
        if len(data) <= 1:
            return data
        pivot = data[len(data) // 2]
        left = [x for x in data if x < pivot]
        middle = [x for x in data if x == pivot]
        right = [x for x in data if x > pivot]
        return self.sort(left) + middle + self.sort(right)

class Sorter:
    """排序器(使用策略模式)"""
    def __init__(self, strategy: SortStrategy):
        self.strategy = strategy

    def sort(self, data):
        return self.strategy.sort(data)

# 使用
data = [64, 34, 25, 12, 22, 11, 90]

# 使用冒泡排序
bubble_sorter = Sorter(BubbleSort())
print(f"冒泡排序: {bubble_sorter.sort(data)}")

# 使用快速排序
quick_sorter = Sorter(QuickSort())
print(f"快速排序: {quick_sorter.sort(data)}")

# 随时可以切换策略
bubble_sorter.strategy = QuickSort()
print(f"切换策略后: {bubble_sorter.sort(data)}")

六、观察者模式(多态实战)

python 复制代码
from abc import ABC, abstractmethod

class Observer(ABC):
    """观察者接口"""
    @abstractmethod
    def update(self, event, data):
        """接收到通知时的处理"""
        pass

class Subject:
    """主题(被观察者)"""
    def __init__(self):
        self._observers = []

    def add_observer(self, observer: Observer):
        self._observers.append(observer)

    def remove_observer(self, observer: Observer):
        self._observers.remove(observer)

    def notify(self, event, data):
        for observer in self._observers:
            observer.update(event, data)

# 具体观察者
class EmailNotifier(Observer):
    def update(self, event, data):
        print(f"📧 邮件通知: {event} - {data}")

class SMSNotifier(Observer):
    def update(self, event, data):
        print(f"📱 短信通知: {event} - {data}")

class LogRecorder(Observer):
    def update(self, event, data):
        print(f"📝 日志记录: {event} - {data}")

# 使用
subject = Subject()
subject.add_observer(EmailNotifier())
subject.add_observer(SMSNotifier())
subject.add_observer(LogRecorder())

subject.notify("新用户注册", "用户ID: 12345")
subject.notify("订单完成", "订单号: 2026001")

七、isinstance() 与抽象类

python 复制代码
from abc import ABC, abstractmethod

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass

class Circle(Drawable):
    def draw(self):
        print("绘制圆形")

class Line(Drawable):
    def draw(self):
        print("绘制直线")

# isinstance 检查
shape = Circle()
print(isinstance(shape, Drawable))  # True

# 可以安全地调用抽象方法
if isinstance(shape, Drawable):
    shape.draw()  # 绘制圆形

八、实战项目:插件系统

python 复制代码
from abc import ABC, abstractmethod
import json

class Plugin(ABC):
    """插件接口"""

    @property
    @abstractmethod
    def name(self):
        """插件名称"""
        pass

    @property
    @abstractmethod
    def version(self):
        """插件版本"""
        pass

    @abstractmethod
    def execute(self, data):
        """执行插件功能"""
        pass

class JSONFormatter(Plugin):
    """JSON 格式化插件"""

    @property
    def name(self):
        return "JSON格式化"

    @property
    def version(self):
        return "1.0"

    def execute(self, data):
        return json.dumps(data, ensure_ascii=False, indent=2)

class CSVFormatter(Plugin):
    """CSV 格式化插件"""

    @property
    def name(self):
        return "CSV格式化"

    @property
    def version(self):
        return "1.0"

    def execute(self, data):
        if not data:
            return ""
        headers = data[0].keys()
        lines = [",".join(headers)]
        for item in data:
            lines.append(",".join(str(item[h]) for h in headers))
        return "\n".join(lines)

class PluginManager:
    """插件管理器"""

    def __init__(self):
        self.plugins = {}

    def register(self, plugin: Plugin):
        self.plugins[plugin.name] = plugin

    def execute(self, plugin_name, data):
        if plugin_name not in self.plugins:
            raise ValueError(f"插件 '{plugin_name}' 不存在")
        return self.plugins[plugin_name].execute(data)

# 使用
data = [
    {"name": "小明", "age": 20, "score": 95},
    {"name": "小红", "age": 21, "score": 88},
]

manager = PluginManager()
manager.register(JSONFormatter())
manager.register(CSVFormatter())

print(manager.execute("JSON格式化", data))
print(manager.execute("CSV格式化", data))

九、总结

概念 说明
多态 同一接口,不同实现
鸭子类型 关心行为,不关心类型
抽象类 使用 ABC + @abstractmethod 定义
抽象方法 子类必须实现的方法
抽象属性 使用 @property + @abstractmethod
接口 只含抽象方法的抽象类
策略模式 运行时切换算法
观察者模式 一对多的依赖通知

最佳实践

  1. 优先使用鸭子类型 ,而不是 isinstance() 检查
  2. 抽象类定义契约,确保子类实现必要方法
  3. 策略模式是切换算法的首选方案
  4. 避免"if-else 判断类型",用多态替代
  5. 接口应该小而专一,不要大而全

十、练习题

练习 1:数据库连接抽象类

创建 DatabaseConnection 抽象类,定义 connect()execute()close() 方法,实现 MySQLSQLite 两个具体类。

练习 2:命令模式

创建 Command 接口,实现 CreateFileCommandDeleteFileCommand,支持撤销操作。

练习 3:模板方法模式

创建 DataProcessor 抽象类,定义 process() 模板方法(包含 readtransformsave 三个步骤),其中 readsave 是抽象方法,transform 有默认实现。

参考答案

练习 3 参考答案

python 复制代码
from abc import ABC, abstractmethod

class DataProcessor(ABC):
    @abstractmethod
    def read(self):
        pass

    def transform(self, data):
        """默认转换:返回原始数据"""
        return data

    @abstractmethod
    def save(self, data):
        pass

    def process(self):
        """模板方法:固定流程"""
        print("开始处理...")
        data = self.read()
        print(f"读取数据: {data}")
        data = self.transform(data)
        print(f"转换数据: {data}")
        self.save(data)
        print("处理完成")

class CSVProcessor(DataProcessor):
    def read(self):
        return [{"name": "Alice"}, {"name": "Bob"}]

    def save(self, data):
        print(f"保存 CSV: {data}")

class JSONProcessor(DataProcessor):
    def read(self):
        return {"users": ["Alice", "Bob"]}

    def transform(self, data):
        return list(data.get("users", []))

    def save(self, data):
        print(f"保存 JSON: {data}")

csv_processor = CSVProcessor()
csv_processor.process()

json_processor = JSONProcessor()
json_processor.process()

🎉 恭喜你完成了第29天的学习!多态和抽象类让代码更加灵活、可扩展。

📌 下一天预告:第30天 ------ Python面向对象之类属性与方法(私有方法、私有属性)------ 基础篇最后一天!

相关推荐
Chester_19991 小时前
CSP202312C.树上搜索
开发语言·数据结构·c++·蓝桥杯·stl
Figo_Cheung1 小时前
Figo共振网络宇宙演化论(RNC) :从原初对称破缺到全息大和谐的演化路径研究
开发语言·php·量子计算
重生之我来学Python1 小时前
Git-SVN 混合开发,从入门到精通!
开发语言·git·svn·github
CoderYanger1 小时前
前端基础——JavaScript(基础语法)(下篇)
java·开发语言·前端·javascript·程序人生·面试·职场和发展
京师20万禁军教头1 小时前
39面向对象(高级)-设计模式
java·开发语言·设计模式
2601_962381581 小时前
ArcGIS+Python+AI赋能高标准农田建设项目审计提质增效
python·arcgis·ai·高标准农田审计·大数据审计
rust&python2 小时前
课程表的归一化处理
python·程序人生·信息可视化
白山编程大哥2 小时前
Java 迭代器接口详解:从 Iterator 到 ListIterator 的完整指南
java·开发语言·windows
见叶之秋2 小时前
【C++】String类的使用(含模拟实现)
java·开发语言·c++