首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

Python 類裝飾器完整指南:註冊模式、元類替代、單例管理與框架整合實戰

     編輯:更多程式設計知識

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

進階模式:方法攔截 (AOP 風格)


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  # 不會被記錄

類裝飾器 vs 元類 vs `__init_subclass__` 比較

特性類裝飾器 @decorator元類 metaclass__init_subclass__
作用時機類別定義完成時類別建立時 (更早)子類別建立時
繼承行為不自動套用至子類自動套用至所有子類專為繼承設計
多重套用可堆疊多個單一元類 (需合併)可結合 super()
修改類別結構可動態增刪屬性完全控制建立過程可修改子類屬性
適用場景註冊、單例、AOP、配置注入ORM、框架核心、DSL介面驗證、預設值、註冊
可讀性高 (明確可見)低 (隱式)中

__init_subclass__ 現代替代方案 (Python 3.6+)


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}

實戰框架整合:FastAPI 依賴注入風格


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()

最佳實務檢查清單

  • ✅ 裝飾器必須回傳類別 (原類別或新類別)
  • ✅ 保留原類別中繼資料:`__name__`、`__module__`、`__doc__`、`__annotations__`
  • ✅ 使用 `functools.wraps` (針對方法) 或手動複製屬性
  • ✅ 避免在裝飾器執行複雜副作用 (I/O、資料庫連線)
  • ✅ 文檔說明裝飾器行為:是否修改繼承、是否可堆疊、執行順序
  • ✅ 單元測試驗證:裝飾後類別行為、註冊中心狀態、單例身分
  • ✅ 考慮 `__init_subclass__` 替代簡單註冊需求
  • ✅ 類別裝飾器執行順序:由內而外 (最接近類別的先執行)

善用類裝飾器實現註冊系統、單例管理、AOP 攔截與配置驅動,能構建高度可擴充、低耦合的 Python 框架架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved