4.2 抽象基類 ABC
4.2 抽象基類 ABC
抽象基類(Abstract Base Class,ABC)用於定義介面契約,確保子類別實作必要的方法。這在建立可擴展的框架時特別有用。
為什麼需要抽象基類?
問題場景
假設我們有多種檔案解析器:
1class JsonParser:
2 def parse(self, content: str) -> dict:
3 return json.loads(content)
4
5class YamlParser:
6 def parse(self, content: str) -> dict:
7 return yaml.safe_load(content)
8
9class XmlParser:
10 # 忘記實作 parse 方法!
11 def read(self, content: str) -> dict: # 方法名稱錯誤
12 ...問題:沒有強制的介面,容易出錯。
使用 ABC 解決
1from abc import ABC, abstractmethod
2
3class BaseParser(ABC):
4 """解析器抽象基類"""
5
6 @abstractmethod
7 def parse(self, content: str) -> dict:
8 """解析內容並返回字典"""
9 pass
10
11class XmlParser(BaseParser):
12 # 如果沒有實作 parse,會報錯
13 def parse(self, content: str) -> dict:
14 ...基本語法
定義抽象基類
1from abc import ABC, abstractmethod
2from typing import Optional
3
4class BaseParser(ABC):
5 """解析器基類"""
6
7 def __init__(self, encoding: str = "utf-8"):
8 self.encoding = encoding
9
10 @abstractmethod
11 def parse(self, content: str) -> dict:
12 """
13 解析內容(子類別必須實作)
14
15 Args:
16 content: 要解析的內容
17
18 Returns:
19 dict: 解析後的資料
20 """
21 pass
22
23 @abstractmethod
24 def validate(self, content: str) -> bool:
25 """驗證內容格式(子類別必須實作)"""
26 pass
27
28 def parse_file(self, path: str) -> dict:
29 """
30 解析檔案(共用方法)
31
32 這是具體實作,子類別可以直接使用或覆寫。
33 """
34 with open(path, encoding=self.encoding) as f:
35 content = f.read()
36 return self.parse(content)實作子類別
1class JsonParser(BaseParser):
2 """JSON 解析器"""
3
4 def parse(self, content: str) -> dict:
5 return json.loads(content)
6
7 def validate(self, content: str) -> bool:
8 try:
9 json.loads(content)
10 return True
11 except json.JSONDecodeError:
12 return False
13
14class YamlParser(BaseParser):
15 """YAML 解析器"""
16
17 def parse(self, content: str) -> dict:
18 return yaml.safe_load(content) or {}
19
20 def validate(self, content: str) -> bool:
21 try:
22 yaml.safe_load(content)
23 return True
24 except yaml.YAMLError:
25 return False抽象屬性
除了方法,也可以定義抽象屬性:
1from abc import ABC, abstractmethod
2
3class BaseParser(ABC):
4
5 @property
6 @abstractmethod
7 def file_extension(self) -> str:
8 """支援的檔案副檔名"""
9 pass
10
11 @property
12 @abstractmethod
13 def mime_type(self) -> str:
14 """MIME 類型"""
15 pass
16
17class JsonParser(BaseParser):
18
19 @property
20 def file_extension(self) -> str:
21 return ".json"
22
23 @property
24 def mime_type(self) -> str:
25 return "application/json"範本方法模式
ABC 常與範本方法模式搭配使用:
1from abc import ABC, abstractmethod
2
3class DataProcessor(ABC):
4 """資料處理器基類"""
5
6 def process(self, data: str) -> dict:
7 """
8 範本方法:定義處理流程
9
10 1. 驗證
11 2. 前處理
12 3. 解析
13 4. 後處理
14 """
15 # 步驟 1:驗證(子類別實作)
16 if not self.validate(data):
17 raise ValueError("Invalid data")
18
19 # 步驟 2:前處理(可選覆寫)
20 data = self.preprocess(data)
21
22 # 步驟 3:解析(子類別實作)
23 result = self.parse(data)
24
25 # 步驟 4:後處理(可選覆寫)
26 return self.postprocess(result)
27
28 @abstractmethod
29 def validate(self, data: str) -> bool:
30 """驗證資料(子類別必須實作)"""
31 pass
32
33 @abstractmethod
34 def parse(self, data: str) -> dict:
35 """解析資料(子類別必須實作)"""
36 pass
37
38 def preprocess(self, data: str) -> str:
39 """前處理(預設不做任何處理)"""
40 return data
41
42 def postprocess(self, result: dict) -> dict:
43 """後處理(預設不做任何處理)"""
44 return result實作檢查
無法直接實例化
1class BaseParser(ABC):
2 @abstractmethod
3 def parse(self, content: str) -> dict:
4 pass
5
6# 錯誤:無法實例化抽象類別
7parser = BaseParser() # TypeError!必須實作所有抽象方法
1class IncompleteParser(BaseParser):
2 # 沒有實作 parse 方法
3 pass
4
5# 錯誤:無法實例化
6parser = IncompleteParser() # TypeError!檢查子類別關係
1from abc import ABC
2
3class BaseParser(ABC):
4 pass
5
6class JsonParser(BaseParser):
7 pass
8
9# 使用 isinstance 和 issubclass
10parser = JsonParser()
11isinstance(parser, BaseParser) # True
12issubclass(JsonParser, BaseParser) # True與 Protocol 的比較
Python 3.8+ 引入了 Protocol,提供結構化子型別(Structural Subtyping):
ABC(名義子型別)
1from abc import ABC, abstractmethod
2
3class Parseable(ABC):
4 @abstractmethod
5 def parse(self, content: str) -> dict:
6 pass
7
8# 必須明確繼承
9class JsonParser(Parseable): # 必須繼承 Parseable
10 def parse(self, content: str) -> dict:
11 return json.loads(content)Protocol(結構化子型別)
1from typing import Protocol
2
3class Parseable(Protocol):
4 def parse(self, content: str) -> dict:
5 ...
6
7# 不需要繼承,只要有相同的方法
8class JsonParser: # 不需要繼承
9 def parse(self, content: str) -> dict:
10 return json.loads(content)
11
12def process(parser: Parseable) -> None:
13 parser.parse("...")
14
15# JsonParser 自動符合 Parseable 協議
16process(JsonParser()) # OK何時使用哪個?
| 特性 | ABC | Protocol |
|---|---|---|
| 需要繼承 | 是 | 否 |
| 執行時檢查 | 支援 | 有限支援 |
| 可以有共用方法 | 是 | 是(3.8+) |
| 適合場景 | 框架設計 | 型別提示 |
最佳實踐
1. 保持介面簡潔
1# 好:專注於核心方法
2class BaseParser(ABC):
3 @abstractmethod
4 def parse(self, content: str) -> dict:
5 pass
6
7# 不好:太多抽象方法
8class BaseParser(ABC):
9 @abstractmethod
10 def parse(self): pass
11 @abstractmethod
12 def validate(self): pass
13 @abstractmethod
14 def preprocess(self): pass
15 @abstractmethod
16 def postprocess(self): pass
17 @abstractmethod
18 def format(self): pass2. 提供有用的預設實作
1class BaseParser(ABC):
2 @abstractmethod
3 def parse(self, content: str) -> dict:
4 pass
5
6 # 有預設實作的方法
7 def parse_file(self, path: str) -> dict:
8 with open(path) as f:
9 return self.parse(f.read())3. 清晰的文檔
1class BaseParser(ABC):
2 """
3 解析器抽象基類
4
5 所有解析器都應繼承此類別並實作 parse 方法。
6
7 Example:
8 class MyParser(BaseParser):
9 def parse(self, content: str) -> dict:
10 return {"data": content}
11 """
12
13 @abstractmethod
14 def parse(self, content: str) -> dict:
15 """
16 解析內容
17
18 Args:
19 content: 要解析的字串內容
20
21 Returns:
22 dict: 解析後的字典
23
24 Raises:
25 ParseError: 如果解析失敗
26 """
27 pass思考題
- 什麼時候應該使用 ABC,什麼時候使用 Protocol?
- 抽象方法可以有實作嗎?有什麼用途?
- 如何測試抽象基類?
實作練習
- 設計一個
BaseValidator抽象基類,定義驗證器的介面 - 實作兩個繼承自
BaseValidator的具體驗證器 - 使用範本方法模式實作一個多步驟的資料處理流程
延伸閱讀(進階系列)
- Descriptor Protocol 完整指南 - 理解 @property 背後的原理
- 插件系統設計 - 用 ABC 建構可擴展的插件架構
- 元編程 - ABC 背後的 metaclass 機制