Python 的 `abc` (Abstract Base Classes) 模組提供正式的介面定義與實作強制機制,是大型專案架構設計、外掛系統、框架開發的核心工具。本文完整解析 ABC 定義、抽象方法/屬性、虛擬子類註冊與進階應用模式。
from abc import ABCMeta, abstractmethod, abstractproperty, abstractclassmethod, abstractstaticmethod
# Python 3.3+ 建議直接繼承 abc.ABC
from abc import ABC
# 兩種定義方式 (Python 3.4+ 推薦方式 2)
class PluginBase(metaclass=ABCMeta): # 方式 1: 明確指定元類
pass
class PluginBase(ABC): # 方式 2: 繼承 ABC (內部使用 ABCMeta)
pass
from abc import ABC, abstractmethod, abstractproperty
class DataProcessor(ABC):
"""資料處理器介面"""
@abstractmethod
def process(self, data: bytes) -> dict:
"""核心處理邏輯,子類必須實作"""
pass
@abstractmethod
def validate(self, data: bytes) -> bool:
"""資料驗證"""
pass
@abstractproperty
def supported_formats(self) -> list[str]:
"""支援的格式列表"""
pass
@abstractclassmethod
def from_config(cls, config: dict) -> 'DataProcessor':
"""工廠方法:從設定建立實例"""
pass
@abstractstaticmethod
def get_default_config() -> dict:
"""預設設定"""
pass
# 正確實作
class JsonProcessor(DataProcessor):
@property
def supported_formats(self) -> list[str]:
return ['json', 'jsonl']
def validate(self, data: bytes) -> bool:
try:
import json
json.loads(data)
return True
except:
return False
def process(self, data: bytes) -> dict:
import json
return json.loads(data)
@classmethod
def from_config(cls, config: dict) -> 'JsonProcessor':
return cls()
@staticmethod
def get_default_config() -> dict:
return {"encoding": "utf-8"}
# 錯誤示範:未實作所有抽象成員
class IncompleteProcessor(DataProcessor):
def process(self, data): pass
# 缺少 validate, supported_formats 等
# 測試
try:
p = IncompleteProcessor() # TypeError: Can't instantiate abstract class...
except TypeError as e:
print(f"預期錯誤: {e}")
# 正確使用
p = JsonProcessor()
print(p.process(b'{"key": "value"}'))
無需繼承,即可讓現有類別成為 ABC 的子類 (僅影響 `isinstance`/`issubclass` 檢查):
from abc import ABC, abstractmethod
class Serializable(ABC):
@abstractmethod
def serialize(self) -> bytes: pass
@abstractmethod
def deserialize(self, data: bytes): pass
# 現有類別,無法修改繼承關係
class LegacyData:
def __init__(self, value):
self.value = value
def to_bytes(self):
return str(self.value).encode()
@classmethod
def from_bytes(cls, data):
return cls(data.decode())
# 註冊為虛擬子類 (不檢查抽象方法實作!)
Serializable.register(LegacyData)
# 驗證
obj = LegacyData(42)
print(isinstance(obj, Serializable)) # True
print(issubclass(LegacyData, Serializable)) # True
# 但無法直接當介面使用 (未實作抽象方法)
try:
s: Serializable = obj
s.serialize() # AttributeError: 'LegacyData' object has no attribute 'serialize'
except AttributeError as e:
print(f"需適配器模式: {e}")
# 適配器模式橋接
class LegacyAdapter(Serializable):
def __init__(self, legacy: LegacyData):
self._legacy = legacy
def serialize(self) -> bytes:
return self._legacy.to_bytes()
def deserialize(self, data: bytes):
self._legacy = LegacyData.from_bytes(data)
from abc import ABC, abstractmethod
from typing import Dict, Type
import importlib
import pkgutil
class Plugin(ABC):
"""外掛基礎介面"""
@property
@abstractmethod
def name(self) -> str: pass
@property
@abstractmethod
def version(self) -> str: pass
@abstractmethod
def execute(self, context: dict) -> dict: pass
@classmethod
@abstractmethod
def get_dependencies(cls) -> list[str]: pass
class PluginRegistry:
"""外掛註冊與發現中心"""
def __init__(self):
self._plugins: Dict[str, Type[Plugin]] = {}
self._instances: Dict[str, Plugin] = {}
def register(self, plugin_class: Type[Plugin]) -> None:
if not issubclass(plugin_class, Plugin):
raise TypeError("必須繼承 Plugin 基類")
# 實例化以取得 name (或要求類別屬性)
instance = plugin_class()
if instance.name in self._plugins:
raise ValueError(f"外掛名稱衝突: {instance.name}")
self._plugins[instance.name] = plugin_class
print(f"註冊外掛: {instance.name} v{instance.version}")
def get_plugin(self, name: str) -> Plugin:
if name not in self._instances:
self._instances[name] = self._plugins[name]()
return self._instances[name]
def auto_discover(self, package_name: str) -> None:
"""自動發現套件中的外掛"""
package = importlib.import_module(package_name)
for _, module_name, _ in pkgutil.iter_modules(package.__path__):
full_name = f"{package_name}.{module_name}"
module = importlib.import_module(full_name)
for attr_name in dir(module):
attr = getattr(module, attr_name)
if (isinstance(attr, type) and
issubclass(attr, Plugin) and
attr is not Plugin):
self.register(attr)
# 具體外掛實作
class ImageResizePlugin(Plugin):
@property
def name(self) -> str: return "image_resize"
@property
def version(self) -> str: return "1.0.0"
def execute(self, context: dict) -> dict:
# 實際處理邏輯
return {"status": "resized", "width": context.get("width")}
@classmethod
def get_dependencies(cls) -> list[str]: return ["pillow"]
# 使用
registry = PluginRegistry()
registry.register(ImageResizePlugin)
plugin = registry.get_plugin("image_resize")
result = plugin.execute({"width": 800})
標準庫提供常用資料結構的 ABC,支援鴨子型別檢查:
from collections.abc import Iterable, Iterator, Sequence, Mapping, Callable, Container
# 判斷物件類型
def process_items(items):
if isinstance(items, Mapping):
print("字典類型")
elif isinstance(items, Sequence) and not isinstance(items, str):
print("序列類型 (list, tuple 等)")
elif isinstance(items, Iterator):
print("迭代器 (一次性)")
elif isinstance(items, Iterable):
print("可迭代物件")
elif isinstance(items, Callable):
print("可呼叫物件")
else:
print("其他")
# 自定義類別實作介面
class MyRange(Sequence):
def __init__(self, start, end, step=1):
self.start, self.end, self.step = start, end, step
def __len__(self):
return max(0, (self.end - self.start + self.step - 1) // self.step)
def __getitem__(self, index):
if index < 0: index += len(self)
if index >= len(self): raise IndexError
return self.start + index * self.step
r = MyRange(0, 10, 2)
print(list(r)) # [0, 2, 4, 6, 8]
print(isinstance(r, Sequence)) # True
print(2 in r) # True (Container 介面)
from typing import Protocol
from abc import abstractmethod
# Protocol 結構化子類型 (靜態檢查,執行時無強制)
class Drawable(Protocol):
@abstractmethod
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("畫圓")
def render(obj: Drawable):
obj.draw()
render(Circle()) # 靜態類型檢查通過,執行時正常
# render("not drawable") # 型別檢查器會報錯
| 特性 | abc.ABC | typing.Protocol | Java Interface |
|---|---|---|---|
| 執行時強制 | 是 (實例化檢查) | 否 (僅靜態檢查) | 是 |
| 多重繼承 | 支援 | 支援 (隱式) | 支援 |
| 預設實作 | 可提供 | 不可 (3.14+ 可用 `@runtime_checkable`) | 可 (default method) |
| 虛擬子類 | register() 支援 | 不支援 | 不支援 |
| 適用場景 | 框架核心、外掛系統、執行時驗證 | 鴨子型別、靜態分析、輕量介面 | 強型別語言標準 |
善用 ABC 定義清晰契約,配合虛擬子類註冊與 Protocol 靜態檢查,能構建高內聚、低耦合、可擴充的 Python 系統架構。