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

Python 裝飾器 完整入門:從閉包原理到實戰 Logger 實作

     編輯:PHP 相關知識

前言

裝飾器 是 Python 最具特色也最實用的語法糖之一,廣泛應用於日誌記錄、效能計時、權限驗證、快取、重試機制等橫切關注點。本文從閉包原理層層遞進,帶你徹底掌握裝飾器的設計與應用。

一、核心基石:閉包

閉包讓內層函數能「記住」並存取外層函數的變數,即使外層函數已執行結束。

基本範例

def outer(x):
    y = [1, 2, 3]          # 外層變數
    def inner():
        print(x)           # 存取外層參數
        print(y)           # 存取外層區域變數
    return inner           # 回傳內層函數物件

x = 5                      # 全域變數,不會被 inner 捕獲
f = outer(2)               # outer 結束,但 inner 仍保有對 x=2, y=[1,2,3] 的引用
f()                        # 輸出:2  /  [1, 2, 3]

# 檢視閉包捕獲的變數
print(f.__closure__)       # (<cell at 0x...: int object at 0x...>, <cell at 0x...: list object at 0x...>)

關鍵點:inner 與其捕獲的環境變數(x、y)共同構成了閉包。外層函數結束後,這些變數不會被銷毀,因為 inner 仍持有引用。

二、裝飾器本質:高階函數

裝飾器本質是一個可呼叫物件,接收一個函數,回傳一個新函數(通常包裝原函數並增加額外行為)。

最簡裝飾器

def entrance(func):
    def inner():
        print(f"呼叫函數: {func.__name__}")
        func()             # 執行原函數
    return inner

# 手動裝飾
def fun1():
    print("fun1 執行中...")

fun1 = entrance(fun1)      # 相當於 fun1 = inner
fun1()                     # 輸出:呼叫函數: fun1  /  fun1 執行中...

@ 語法糖

@entrance
def fun2():
    print("fun2 執行中...")

# 等價於 fun2 = entrance(fun2)
fun2()

三、進階:支援任意參數的通用裝飾器

基本版裝飾器無法處理帶參數的函數,需使用 *args、**kwargs:

def entrance(func):
    def inner(*args, **kwargs):
        print(f"呼叫函數: {func.__name__}, 參數: {args}, {kwargs}")
        return func(*args, **kwargs)   # 透傳參數並回傳結果
    return inner

@entrance
def add(a, b):
    return a + b

result = add(3, 5)
# 輸出:呼叫函數: add, 參數: (3, 5), {}
# result = 8

四、實戰:打造 Logger 裝飾器

記錄函數呼叫名稱、參數、回傳值、執行時間:

import time
import functools

def logger(func):
    @functools.wraps(func)          # 保留原函數元資料(__name__, __doc__ 等)
    def inner(*args, **kwargs):
        start = time.perf_counter()
        print(f"[LOG] {func.__name__} 開始執行 | 參數: {args} {kwargs}")
        
        result = func(*args, **kwargs)   # 執行原函數
        
        elapsed = time.perf_counter() - start
        print(f"[LOG] {func.__name__} 結束 | 耗時: {elapsed:.4f}s | 回傳: {result}")
        return result
    return inner

# 使用範例
@logger
def calculate_tax(price, rate=0.05):
    """計算含稅價格"""
    time.sleep(0.1)  # 模擬運算
    return round(price * (1 + rate), 2)

@logger
def greet(name, *, formal=False):
    prefix = "敬愛的" if formal else "哈囉"
    return f"{prefix} {name}"

# 測試
print(calculate_tax(1000))
print(greet("小明", formal=True))

輸出結果

[LOG] calculate_tax 開始執行 | 參數: (1000,) {'rate': 0.05}
[LOG] calculate_tax 結束 | 耗時: 0.1003s | 回傳: 1050.0
1050.0
[LOG] greet 開始執行 | 參數: ('小明',) {'formal': True}
[LOG] greet 結束 | 耗時: 0.0001s | 回傳: 敬愛的 小明
敬愛的 小明

五、進階主題

1. 帶參數的裝飾器(裝飾器工廠)

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"第 {attempt} 次失敗: {e}, {delay}s 後重試...")
                    time.sleep(delay)
        return inner
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("網路不穩")
    return "成功"

print(unstable_api())

2. 類別裝飾器

class Timer:
    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)
    
    def __call__(self, *args, **kwargs):
        start = time.perf_counter()
        result = self.func(*args, **kwargs)
        print(f"{self.func.__name__} 耗時: {time.perf_counter() - start:.4f}s")
        return result

@Timer
def slow():
    time.sleep(0.5)

3. 保留函數簽名

使用 functools.wraps 可保留 __name__、__doc__、__annotations__ 等屬性,讓 help()、IDE 提示、序列化正常運作。

六、常見陷阱與最佳實踐

陷阱解決方案
忘記 @functools.wraps永遠加上,避免除錯、文檔、序列化異常
裝飾器順序錯誤@a @b def f() 等價 a(b(f)),由內而外執行
修改可變預設參數勿在裝飾器內修改 **kwargs 直接引用的字典
非同步函數裝飾需定義 async def inner 並 await func()
過度使用導致堆疊過深合併邏輯相關的裝飾器,或用中介軟體模式

七、標準庫與生態系常見裝飾器

  • @property、@classmethod、@staticmethod:類別方法轉換
  • @functools.lru_cache:記憶化快取
  • @dataclass:資料類別自動生成 __init__ 等
  • @contextlib.contextmanager:產生器變上下文管理器
  • Flask @app.route、FastAPI @app.get:路由註冊
  • Celery @task、Dramatiq @actor:非同步任務

總結

裝飾器 = 閉包 + 高階函數 + 語法糖。掌握 *args/**kwargs 透傳、functools.wraps 保留元資料、帶參數裝飾器工廠模式,即可靈活處理日誌、快取、重試、計時、權限等橫切關注點,寫出乾淨、可復用、符合單一職責原則的 Python 程式碼。

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