Python 類裝飾器 (`@decorator` 作用於 `class`) 是框架設計、元編程、註冊系統的核心工具。本文系統性解析類裝飾器機制、註冊模式、單例實作、屬性注入與進階框架整合模式。
# 這兩種寫法等價
@register
class MyClass:
pass
# 等同於
class MyClass:
pass
MyClass = register(MyClass)
# 裝飾器簽名
def register(cls):
# cls 是被裝飾的類別物件
# 執行註冊邏輯
return cls # 必須回傳類別 (或新類別)
# registry.py
_registry = {}
def register(cls):
"""類裝飾器:自動註冊類別"""
key = getattr(cls, '__component_id__', cls.__name__)
if key in _registry:
raise ValueError(f"重複註冊: {key}")
_registry[key] = cls
return cls
def get_component(name):
return _registry.get(name)
def list_components():
return list(_registry.keys())
# 使用範例
@register
class DatabaseComponent:
__component_id__ = 'db.postgresql'
def connect(self): pass
@register
class CacheComponent:
__component_id__ = 'cache.redis'
def get(self, key): pass
# 取得元件
db_cls = get_component('db.postgresql')
db = db_cls()
def add_timestamp(cls):
"""為類別添加建立時間戳記屬性"""
original_init = cls.__init__
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
import time
self._created_at = time.time()
cls.__init__ = new_init
cls.created_at = property(lambda self: self._created_at)
return cls
@add_timestamp
class DataRecord:
def __init__(self, value):
self.value = value
r = DataRecord(42)
print(r.value) # 42
print(r.created_at) # 時間戳
def singleton(cls):
"""線程安全單例裝飾器"""
import threading
_instances = {}
_lock = threading.Lock()
def get_instance(*args, **kwargs):
if cls not in _instances:
with _lock:
if cls not in _instances: # Double-checked locking
_instances[cls] = cls(*args, **kwargs)
return _instances[cls]
# 替換類別的 __call__ (使類別可呼叫回傳單例)
cls.__call__ = staticmethod(get_instance)
# 防止直接實例化
original_new = cls.__new__
def controlled_new(cls_, *args, **kwargs):
if cls_ is cls:
return get_instance(*args, **kwargs)
return original_new(cls_, *args, **kwargs)
cls.__new__ = staticmethod(controlled_new)
return cls
@singleton
class ConfigManager:
def __init__(self):
self.settings = {}
def set(self, key, value):
self.settings[key] = value
def get(self, key, default=None):
return self.settings.get(key, default)
# 使用
config1 = ConfigManager() # 實際回傳單例
config2 = ConfigManager()
print(config1 is config2) # True
config1.set('debug', True)
print(config2.get('debug')) # True
from abc import ABCMeta, abstractmethod
def implements(interface):
"""驗證類別實作指定介面 (所有抽象方法)"""
def decorator(cls):
if not issubclass(interface, ABCMeta):
raise TypeError("介面必須是 ABC")
# 檢查所有抽象方法
missing = []
for method in interface.__abstractmethods__:
if not hasattr(cls, method):
missing.append(method)
elif getattr(cls, method) is getattr(interface, method):
missing.append(method) # 未覆寫
if missing:
raise TypeError(f"{cls.__name__} 未實作介面方法: {missing}")
# 標記實作關係
cls._implements_ = getattr(cls, '_implements_', [])
cls._implements_.append(interface)
return cls
return decorator
# 定義介面
class IRepository(metaclass=ABCMeta):
@abstractmethod
def save(self, entity): pass
@abstractmethod
def find_by_id(self, id): pass
# 實作 (通過驗證)
@implements(IRepository)
class UserRepository:
def save(self, entity): pass
def find_by_id(self, id): pass
# 實作失敗 (缺少方法)
@implements(IRepository)
class BadRepository:
def save(self, entity): pass
# 缺少 find_by_id -> 裝飾時拋出 TypeError
def configurable(config_key=None):
"""從設定檔注入屬性"""
def decorator(cls):
# 假設全域設定
from myapp import settings
cfg = getattr(settings, config_key or cls.__name__.upper(), {})
for key, value in cfg.items():
setattr(cls, key, value)
cls._config = cfg
return cls
return decorator
@configurable('DATABASE')
class Database:
# 類別屬性將被設定檔覆蓋/補充
host = 'localhost'
port = 5432
pool_size = 10
# settings.DATABASE = {'host': 'prod.db', 'pool_size': 20}
# Database.host -> 'prod.db'
# Database.pool_size -> 20
import functools
import time
import logging
def logged(methods=None, level=logging.INFO):
"""為指定方法添加日誌"""
def decorator(cls):
target_methods = methods or [
name for name in dir(cls)
if callable(getattr(cls, name)) and not name.startswith('_')
]
for name in target_methods:
original = getattr(cls, name)
@functools.wraps(original)
def wrapper(self, *args, _method=original, _name=name, **kwargs):
logging.log(level, f"呼叫 {cls.__name__}.{_name}({args}, {kwargs})")
start = time.time()
try:
result = _method(self, *args, **kwargs)
logging.log(level, f"{_name} 耗時 {time.time()-start:.3f}s")
return result
except Exception as e:
logging.exception(f"{_name} 異常: {e}")
raise
setattr(cls, name, wrapper)
return cls
return decorator
@logged(methods=['process', 'save'])
class DataService:
def process(self, data):
time.sleep(0.1)
return data.upper()
def save(self, data):
time.sleep(0.05)
return True
def internal_helper(self):
pass # 不會被記錄
| 特性 | 類裝飾器 @decorator | 元類 metaclass | __init_subclass__ |
|---|---|---|---|
| 作用時機 | 類別定義完成時 | 類別建立時 (更早) | 子類別建立時 |
| 繼承行為 | 不自動套用至子類 | 自動套用至所有子類 | 專為繼承設計 |
| 多重套用 | 可堆疊多個 | 單一元類 (需合併) | 可結合 super() |
| 修改類別結構 | 可動態增刪屬性 | 完全控制建立過程 | 可修改子類屬性 |
| 適用場景 | 註冊、單例、AOP、配置注入 | ORM、框架核心、DSL | 介面驗證、預設值、註冊 |
| 可讀性 | 高 (明確可見) | 低 (隱式) | 中 |
class PluginBase:
_plugins = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 自動註冊所有子類
name = getattr(cls, 'plugin_name', cls.__name__.lower())
if name in cls._plugins:
raise ValueError(f"外掛名稱衝突: {name}")
cls._plugins[name] = cls
print(f"自動註冊外掛: {name}")
@classmethod
def get_plugin(cls, name):
return cls._plugins.get(name)
# 使用:無需裝飾器,繼承即自動註冊
class PdfExporter(PluginBase):
plugin_name = 'pdf'
def export(self, data): pass
class ExcelExporter(PluginBase):
plugin_name = 'excel'
def export(self, data): pass
print(PluginBase._plugins) # {'pdf': PdfExporter, 'excel': ExcelExporter}
from typing import Type, TypeVar
from functools import lru_cache
T = TypeVar('T')
class ServiceContainer:
_services = {}
_singletons = {}
@classmethod
def register(cls, interface: Type[T], implementation: Type[T] = None,
singleton: bool = False):
impl = implementation or interface
cls._services[interface] = (impl, singleton)
@classmethod
def get(cls, interface: Type[T]) -> T:
if interface not in cls._services:
raise KeyError(f"未註冊服務: {interface}")
impl, singleton = cls._services[interface]
if singleton:
if interface not in cls._singletons:
cls._singletons[interface] = impl()
return cls._singletons[interface]
return impl()
# 裝飾器簡化註冊
def service(interface=None, singleton=False):
def decorator(impl):
target = interface or impl
ServiceContainer.register(target, impl, singleton)
return impl
return decorator
# 使用
@ServiceInterface
class ServiceInterface:
def do_work(self): pass
@ServiceInterface # 實作介面
class ServiceImpl:
def do_work(self): print("工作中")
# 取得服務
svc = ServiceContainer.get(ServiceInterface)
svc.do_work()
善用類裝飾器實現註冊系統、單例管理、AOP 攔截與配置驅動,能構建高度可擴充、低耦合的 Python 框架架構。