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更规范,接口更明确。
练习题:
- 创建一个
Vehicle抽象基类,定义交通工具的基本接口 - 实现一个文件处理器抽象类,支持txt、csv、json格式
- 设计一个日志系统,使用ABC定义Logger接口
思考题:
为什么Python既支持鸭子类型又支持ABC?它们各自的应用场景是什么?
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
