Python教程37:抽象基类(ABC)

“约定优于配置。”

抽象基类(Abstract Base Class, ABC)是Python中定义接口规范的机制。今天我们学习如何使用abc模块创建抽象类,强制子类实现特定方法。

1. 什么是抽象基类

问题场景

没有抽象类时,接口规范靠文档和约定:

 1class Shape:
 2    """
 3    形状基类
 4    - 子类应该实现area()和perimeter()
 5    - 但没有强制机制
 6    """
 7    def area(self):
 8        raise NotImplementedError("子类必须实现area方法")
 9    
10    def perimeter(self):
11        raise NotImplementedError("子类必须实现perimeter方法")
12
13class Circle(Shape):
14    def __init__(self, radius):
15        self.radius = radius
16    
17    def area(self):
18        import math
19        return math.pi * self.radius ** 2
20    
21    # 忘记实现perimeter()了!
22
23# 问题:可以实例化不完整的类
24c = Circle(5)
25print(c.area())  # OK
26# print(c.perimeter())  # 运行时才报错!

抽象基类解决方案

 1from abc import ABC, abstractmethod
 2
 3class Shape(ABC):
 4    """
 5    抽象形状类
 6    - 继承ABC
 7    - 使用@abstractmethod标记抽象方法
 8    - 不能直接实例化
 9    - 强制子类实现所有抽象方法
10    """
11    
12    @abstractmethod
13    def area(self):
14        """计算面积(抽象方法)"""
15        pass
16    
17    @abstractmethod
18    def perimeter(self):
19        """计算周长(抽象方法)"""
20        pass
21
22class Circle(Shape):
23    def __init__(self, radius):
24        self.radius = radius
25    
26    def area(self):
27        import math
28        return math.pi * self.radius ** 2
29    
30    # 如果不实现perimeter(),无法实例化
31
32# circle = Shape()  # TypeError: 无法实例化抽象类
33
34class CompleteCircle(Shape):
35    def __init__(self, radius):
36        self.radius = radius
37    
38    def area(self):
39        import math
40        return math.pi * self.radius ** 2
41    
42    def perimeter(self):
43        import math
44        return 2 * math.pi * self.radius
45
46# 完整实现所有抽象方法才能实例化
47c = CompleteCircle(5)
48print(c.area())       # 78.54
49print(c.perimeter())  # 31.42

ABC的优势

  • 编译时检查:实例化时就发现问题
  • 接口规范:明确定义必须实现的方法
  • 文档作用:清晰表达设计意图
  • 防止误用:不能实例化不完整的类

2. 创建抽象基类

基本语法

 1from abc import ABC, abstractmethod
 2
 3class AbstractClass(ABC):
 4    """
 5    抽象基类
 6    1. 继承ABC
 7    2. 用@abstractmethod装饰抽象方法
 8    """
 9    
10    @abstractmethod
11    def abstract_method(self):
12        """抽象方法(子类必须实现)"""
13        pass
14    
15    def concrete_method(self):
16        """具体方法(子类可以不实现)"""
17        print("这是具体方法")

抽象方法

 1from abc import ABC, abstractmethod
 2
 3class Animal(ABC):
 4    """动物抽象类"""
 5    
 6    @abstractmethod
 7    def make_sound(self):
 8        """发出声音(抽象)"""
 9        pass
10    
11    @abstractmethod
12    def move(self):
13        """移动(抽象)"""
14        pass
15    
16    def eat(self):
17        """吃东西(具体方法)"""
18        print(f"{self.__class__.__name__} is eating")
19
20class Dog(Animal):
21    """狗类"""
22    def make_sound(self):
23        return "Woof!"
24    
25    def move(self):
26        return "Running on four legs"
27
28class Bird(Animal):
29    """鸟类"""
30    def make_sound(self):
31        return "Chirp!"
32    
33    def move(self):
34        return "Flying with wings"
35
36# 使用
37dog = Dog()
38print(dog.make_sound())  # Woof!
39print(dog.move())        # Running on four legs
40dog.eat()                # Dog is eating
41
42bird = Bird()
43print(bird.make_sound())  # Chirp!
44print(bird.move())        # Flying with wings

抽象属性

 1from abc import ABC, abstractmethod
 2
 3class Person(ABC):
 4    """人类抽象基类"""
 5    
 6    @property
 7    @abstractmethod
 8    def name(self):
 9        """姓名(抽象属性)"""
10        pass
11    
12    @name.setter
13    @abstractmethod
14    def name(self, value):
15        """姓名setter"""
16        pass
17
18class Student(Person):
19    """学生类"""
20    def __init__(self, name):
21        self._name = name
22    
23    @property
24    def name(self):
25        return self._name
26    
27    @name.setter
28    def name(self, value):
29        if not value:
30            raise ValueError("姓名不能为空")
31        self._name = value
32
33s = Student("Alice")
34print(s.name)  # Alice
35s.name = "Bob"
36print(s.name)  # Bob

3. 实战示例

示例1:支付系统

 1from abc import ABC, abstractmethod
 2
 3class Payment(ABC):
 4    """支付抽象基类"""
 5    
 6    def __init__(self, amount):
 7        self.amount = amount
 8    
 9    @abstractmethod
10    def process_payment(self):
11        """处理支付(抽象)"""
12        pass
13    
14    @abstractmethod
15    def refund(self):
16        """退款(抽象)"""
17        pass
18    
19    def validate_amount(self):
20        """验证金额(具体方法)"""
21        if self.amount <= 0:
22            raise ValueError("金额必须大于0")
23        return True
24
25class CreditCardPayment(Payment):
26    """信用卡支付"""
27    def __init__(self, amount, card_number):
28        super().__init__(amount)
29        self.card_number = card_number
30    
31    def process_payment(self):
32        self.validate_amount()
33        return f"用信用卡{self.card_number}支付${self.amount}"
34    
35    def refund(self):
36        return f"退款${self.amount}到信用卡{self.card_number}"
37
38class PayPalPayment(Payment):
39    """PayPal支付"""
40    def __init__(self, amount, email):
41        super().__init__(amount)
42        self.email = email
43    
44    def process_payment(self):
45        self.validate_amount()
46        return f"通过PayPal({self.email})支付${self.amount}"
47    
48    def refund(self):
49        return f"退款${self.amount}到PayPal账户{self.email}"
50
51# 使用
52payments = [
53    CreditCardPayment(100, "1234-5678-9012-3456"),
54    PayPalPayment(50, "user@example.com")
55]
56
57for payment in payments:
58    print(payment.process_payment())

示例2:数据库连接

 1from abc import ABC, abstractmethod
 2
 3class DatabaseConnection(ABC):
 4    """数据库连接抽象基类"""
 5    
 6    @abstractmethod
 7    def connect(self):
 8        """连接数据库"""
 9        pass
10    
11    @abstractmethod
12    def disconnect(self):
13        """断开连接"""
14        pass
15    
16    @abstractmethod
17    def execute(self, query):
18        """执行查询"""
19        pass
20    
21    def __enter__(self):
22        """上下文管理器进入"""
23        self.connect()
24        return self
25    
26    def __exit__(self, exc_type, exc_val, exc_tb):
27        """上下文管理器退出"""
28        self.disconnect()
29
30class MySQLConnection(DatabaseConnection):
31    """MySQL连接"""
32    def __init__(self, host, database):
33        self.host = host
34        self.database = database
35        self.connection = None
36    
37    def connect(self):
38        print(f"连接MySQL: {self.host}/{self.database}")
39        self.connection = f"MySQL Connection to {self.database}"
40    
41    def disconnect(self):
42        print(f"断开MySQL连接")
43        self.connection = None
44    
45    def execute(self, query):
46        if not self.connection:
47            raise RuntimeError("未连接数据库")
48        return f"执行MySQL查询: {query}"
49
50class PostgreSQLConnection(DatabaseConnection):
51    """PostgreSQL连接"""
52    def __init__(self, host, database):
53        self.host = host
54        self.database = database
55        self.connection = None
56    
57    def connect(self):
58        print(f"连接PostgreSQL: {self.host}/{self.database}")
59        self.connection = f"PostgreSQL Connection to {self.database}"
60    
61    def disconnect(self):
62        print(f"断开PostgreSQL连接")
63        self.connection = None
64    
65    def execute(self, query):
66        if not self.connection:
67            raise RuntimeError("未连接数据库")
68        return f"执行PostgreSQL查询: {query}"
69
70# 使用
71with MySQLConnection("localhost", "testdb") as db:
72    result = db.execute("SELECT * FROM users")
73    print(result)

4. 抽象类的高级用法

部分实现

 1from abc import ABC, abstractmethod
 2
 3class DataProcessor(ABC):
 4    """数据处理器抽象基类"""
 5    
 6    @abstractmethod
 7    def load_data(self):
 8        """加载数据(抽象)"""
 9        pass
10    
11    @abstractmethod
12    def process_data(self):
13        """处理数据(抽象)"""
14        pass
15    
16    def save_data(self, data):
17        """保存数据(具体实现)"""
18        print(f"保存数据: {data}")
19    
20    def run(self):
21        """运行流程(模板方法)"""
22        data = self.load_data()
23        processed = self.process_data()
24        self.save_data(processed)
25
26class CSVProcessor(DataProcessor):
27    """CSV处理器"""
28    def load_data(self):
29        return "CSV数据"
30    
31    def process_data(self):
32        return "处理后的CSV数据"
33
34processor = CSVProcessor()
35processor.run()
36# 输出:
37# 保存数据: 处理后的CSV数据

注册虚拟子类

 1from abc import ABC
 2
 3class MyABC(ABC):
 4    """抽象基类"""
 5    pass
 6
 7class MyConcreteClass:
 8    """具体类(不继承MyABC)"""
 9    pass
10
11# 注册为虚拟子类
12MyABC.register(MyConcreteClass)
13
14# 现在isinstance检查返回True
15obj = MyConcreteClass()
16print(isinstance(obj, MyABC))  # True
17print(issubclass(MyConcreteClass, MyABC))  # True

5. ABC vs 鸭子类型

Python支持鸭子类型,何时用ABC?

 1# 方式1:鸭子类型(Python风格)
 2def process(obj):
 3    """只要有process方法就行"""
 4    obj.process()
 5
 6# 方式2:ABC(明确接口)
 7from abc import ABC, abstractmethod
 8
 9class Processor(ABC):
10    @abstractmethod
11    def process(self):
12        pass
13
14def process(obj: Processor):
15    """明确要求Processor接口"""
16    obj.process()

何时使用ABC

  • 大型项目,需要明确接口规范
  • 多人协作,避免误解
  • 框架设计,定义扩展点
  • 需要编译时检查

何时用鸭子类型

  • 小型项目
  • 快速原型
  • Python风格的灵活性

6. 小结

今天我们学习了抽象基类:

  • 定义:继承ABC,用@abstractmethod
  • 抽象方法:子类必须实现
  • 抽象属性:@property + @abstractmethod
  • 优势:接口规范、编译时检查
  • 实战:支付系统、数据库连接
  • ABC vs 鸭子类型:根据场景选择

抽象基类让Python的OOP更规范,接口更明确。


练习题

  1. 创建一个Vehicle抽象基类,定义交通工具的基本接口
  2. 实现一个文件处理器抽象类,支持txt、csv、json格式
  3. 设计一个日志系统,使用ABC定义Logger接口

思考题

为什么Python既支持鸭子类型又支持ABC?它们各自的应用场景是什么?


本文代码示例

关注公众号:极客老墨

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

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

相关阅读