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

Python 裝飾器完整指南:從基礎語法到參數化、類裝飾器與實戰模式

     編輯:更多程式設計知識

裝飾器是 Python 最具表達力的特性之一,實現了橫切關注點(日誌、計時、事務、權限等)與業務邏輯的分離。本文從語法糖本質講起,逐層遞進至帶參裝飾器、類裝飾器、多層嵌套、functools.wraps 保護元數據等進階主題,並給出生產級最佳實踐模式。

裝飾器本質:高階函數 + 語法糖

@decorator
def func(): ...

# 等價於
func = decorator(func)

decorator 是一個可調用對象,接收原函數,返回新函數(包裝器)。

最簡裝飾器:無參、無返回值處理

def simple_decorator(func):
    def wrapper():
        print("調用前")
        func()
        print("調用後")
    return wrapper

@simple_decorator
def greet():
    print("Hello")

greet()
# 輸出:
# 調用前
# Hello
# 調用後

保護元數據:functools.wraps

直接包裝會導致 __name__、__doc__、__annotations__ 丟失,影響調試與自動化工具。

from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 調用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

處理任意參數:*args, **kwargs

def universal_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"參數: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@universal_decorator
def add(a, b=10):
    return a + b

add(1)           # 參數: (1,), {}
add(2, b=20)     # 參數: (2,), {'b': 20}

帶參數的裝飾器:三層嵌套

def repeat(times):
    """返回一個裝飾器,使被裝飾函數執行 times 次"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(3)
def say_hi(name):
    return f"Hi {name}"

print(say_hi("Alice"))  # ['Hi Alice', 'Hi Alice', 'Hi Alice']

調用鏈:@repeat(3) → repeat(3) 返回 decorator → decorator(say_hi) 返回 wrapper。

類裝飾器:實現 __call__ 與上下文管理

class Timer:
    def __init__(self, label=""):
        self.label = label
        self.start = 0

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            import time
            self.start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed = time.perf_counter() - self.start
            print(f"[{self.label}] {func.__name__} 耗時 {elapsed:.4f}s")
            return result
        return wrapper

    # 額外支援 with 語句
    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self

    def __exit__(self, *exc):
        import time
        print(f"[{self.label}] 區塊耗時 {time.perf_counter() - self.start:.4f}s")

# 作為函數裝飾器
@Timer("API")
def fetch_data():
    pass

# 作為上下文管理器
with Timer("DB"):
    pass

多層裝飾器執行順序:由內而外

@a
@b
@c
def f(): ...

# 等價於
f = a(b(c(f)))
# 調用順序:a.wrapper → b.wrapper → c.wrapper → f

實戰模式:鎖同步裝飾器

import threading
from functools import wraps

def synchronized(lock):
    """參數化裝飾器:接受任意實現 acquire/release 的鎖對象"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            lock.acquire()
            try:
                return func(*args, **kwargs)
            finally:
                lock.release()
        return wrapper
    return decorator

my_lock = threading.RLock()

class Service:
    @synchronized(my_lock)
    def critical_section(self):
        pass

裝飾器堆疊與類方法

class MyClass:
    @classmethod
    @logged          # 先應用 logged,再應用 classmethod
    def foo(cls):
        pass

    @staticmethod
    @logged          # 同理
    def bar():
        pass

    @property
    @logged          # property 必須在最外層
    def baz(self):
        return 1

順序原則:離函數定義最近的裝飾器最先執行。

常見陷阱避坑指南

陷阱解決方案
忘記 @wraps 導致元數據丟失始終在 wrapper 上使用 @wraps(func)
裝飾器改變函數簽名,破壞類型提示使用 typing.ParamSpec + Concatenate (Python 3.10+) 保留簽名
類裝飾器未正確處理 self/clswrapper 使用 *args, **kwargs 透傳,不假設參數結構
多線程下共享狀態導致競態裝飾器內部狀態用 threading.local 或鎖保護
遞歸函數被裝飾導致重複包裝用 functools.lru_cache 或檢測 func.__wrapped__ 避免

標準庫裝飾器速查

  • @property、@classmethod、@staticmethod
  • @functools.lru_cache(maxsize=128):記憶化緩存
  • @functools.singledispatch:單分發泛函數
  • @contextlib.contextmanager:生成器變上下文管理器
  • @dataclasses.dataclass:自動生成 __init__ 等
  • @typing.overload:類型提示重載

總結

裝飾器是「函數接收函數,返回函數」的語法糖,核心價值在於關注點分離與代碼復用。掌握三層嵌套模板、wraps 保護、類裝飾器靈活性,即可輕鬆實現日誌、計時、重試、限流、事務、權限驗證等通用邏輯,讓業務代碼保持純粹。

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