一、簡介
在 Python 編程中,裝飾器(Decorator)是一種強大的語法糖,它允許開發(fā)者在不修改原函數(shù)代碼的情況下,動態(tài)增強函數(shù)功能。這種設計模式廣泛應用于日志記錄、權限驗證、性能測試和緩存等場景,是 Python 元編程的重要組成部分。本文將從基礎概念出發(fā),逐步深入探討裝飾器的各種應用技巧。
二、裝飾器基礎原理
2.1 函數(shù)作為一等公民
在 Python 中,函數(shù)是一等公民(First-Class Citizen),這意味著它們可以:
? ? 作為變量賦值
? ? 作為參數(shù)傳遞
? ? 作為返回值返回
? ? 存儲在數(shù)據(jù)結構中
以下示例展示了函數(shù)作為變量和參數(shù)的用法:
? ? def greet(name):
? ? ? ? return f"Hello, {name}!"
? ? # 將函數(shù)賦值給變量
? ? hello = greet
? ? # 函數(shù)作為參數(shù)傳遞
? ? def call_function(func, arg):
? ? ? ? return func(arg)
? ? print(call_function(hello, "Alice"))? # 輸出: Hello, Alice!
2.2 閉包(Closure)
閉包是裝飾器的核心機制,它指的是一個函數(shù)可以訪問并記住其外部作用域中的變量,即使外部函數(shù)已經(jīng)執(zhí)行完畢。
? ? def outer(x):
? ? ? ? def inner(y):
? ? ? ? ? ? return x + y? # 閉包捕獲了外部變量x
? ? ? ? return inner
? ? add_five = outer(5)
? ? print(add_five(3))? # 輸出: 8
2.3 簡單裝飾器實現(xiàn)
裝飾器本質(zhì)上是一個高階函數(shù),它接收一個函數(shù)作為輸入,并返回一個新的函數(shù)。以下是一個計算函數(shù)執(zhí)行時間的裝飾器示例:
? ? import time
? ? def timer_decorator(func):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? start_time = time.time()
? ? ? ? ? ? result = func(*args, **kwargs)? # 執(zhí)行原函數(shù)
? ? ? ? ? ? end_time = time.time()
? ? ? ? ? ? print(f"函數(shù) {func.__name__} 執(zhí)行耗時: {end_time - start_time:.4f} 秒")
? ? ? ? ? ? return result
? ? ? ? return wrapper
? ? @timer_decorator
? ? def calculate_sum(n):
? ? ? ? return sum(range(n+1))
? ? print(calculate_sum(1000000))
? ? # 輸出:
? ? # 函數(shù) calculate_sum 執(zhí)行耗時: 0.0520 秒
? ? # 500000500000
這個裝飾器的工作原理如下:
? ? timer_decorator 接收 calculate_sum 函數(shù)作為參數(shù)
? ? 內(nèi)部定義了 wrapper 函數(shù)來包裹原函數(shù)
? ? 返回 wrapper 函數(shù)替代原函數(shù)
? ? @timer_decorator 語法糖等價于 calculate_sum = timer_decorator(calculate_sum)
三、裝飾器進階用法
3.1 帶參數(shù)的裝飾器
有時我們需要為裝飾器傳遞額外參數(shù),這可以通過創(chuàng)建一個三層嵌套函數(shù)實現(xiàn):
? ? def repeat(n):
? ? ? ? def decorator(func):
? ? ? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? ? ? result = None
? ? ? ? ? ? ? ? for _ in range(n):
? ? ? ? ? ? ? ? ? ? result = func(*args, **kwargs)
? ? ? ? ? ? ? ? return result
? ? ? ? ? ? return wrapper
? ? ? ? return decorator
? ? @repeat(3)
? ? def say_hi():
? ? ? ? print("Hi!")
? ? say_hi()? # 輸出三次 Hi!
3.2 類裝飾器
除了函數(shù)裝飾器,Python 還支持使用類作為裝飾器。類裝飾器通過實現(xiàn) __call__ 方法來實現(xiàn):
? ? class CountCalls:
? ? ? ? def __init__(self, func):
? ? ? ? ? ? self.func = func
? ? ? ? ? ? self.num_calls = 0
? ? ? ? def __call__(self, *args, **kwargs):
? ? ? ? ? ? self.num_calls += 1
? ? ? ? ? ? print(f"第 {self.num_calls} 次調(diào)用 {self.func.__name__}")
? ? ? ? ? ? return self.func(*args, **kwargs)
? ? @CountCalls
? ? def say_hello():
? ? ? ? print("Hello!")
? ? say_hello()? # 輸出: 第 1 次調(diào)用 say_hello \n Hello!
? ? say_hello()? # 輸出: 第 2 次調(diào)用 say_hello \n Hello!
3.3 保留函數(shù)元信息
當使用裝飾器時,原函數(shù)的元信息(如 __name__、__doc__)會被覆蓋??梢允褂?functools.wraps 來保留這些信息:
? ? import functools
? ? def debug(func):
? ? ? ? @functools.wraps(func)? # 保留原函數(shù)元信息
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? print(f"調(diào)用 {func.__name__}({args}, {kwargs})")
? ? ? ? ? ? result = func(*args, **kwargs)
? ? ? ? ? ? print(f"{func.__name__} 返回 {result}")
? ? ? ? ? ? return result
? ? ? ? return wrapper
? ? @debug
? ? def add(a, b):
? ? ? ? """返回兩數(shù)之和"""
? ? ? ? return a + b
? ? print(add(3, 5))
? ? print(add.__name__)? # 輸出: add (而不是 wrapper)
? ? print(add.__doc__)? # 輸出: 返回兩數(shù)之和
3.4 多個裝飾器疊加
多個裝飾器可以疊加使用,它們會按照從下到上的順序依次應用:
? ? def make_bold(func):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? return f"<b>{func(*args, **kwargs)}</b>"
? ? ? ? return wrapper
? ? def make_italic(func):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? return f"<i>{func(*args, **kwargs)}</i>"
? ? ? ? return wrapper
? ? @make_bold
? ? @make_italic
? ? def greet(name):
? ? ? ? return f"Hello, {name}!"
? ? print(greet("Alice"))? # 輸出: <b><i>Hello, Alice!</i></b>
四、實戰(zhàn)應用案例
4.1 日志記錄
自動記錄函數(shù)調(diào)用和返回值:
? ? def log_decorator(func):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? result = func(*args, **kwargs)
? ? ? ? ? ? with open('app.log', 'a') as f:
? ? ? ? ? ? ? ? f.write(f"{func.__name__}({args}, {kwargs}) -> {result}\n")
? ? ? ? ? ? return result
? ? ? ? return wrapper
? ? @log_decorator
? ? def calculate(a, b):
? ? ? ? return a * b
4.2 權限驗證
在執(zhí)行敏感操作前驗證用戶權限:
? ? def requires_admin(func):
? ? ? ? def wrapper(user, *args, **kwargs):
? ? ? ? ? ? if user.get('role') != 'admin':
? ? ? ? ? ? ? ? raise PermissionError("需要管理員權限")
? ? ? ? ? ? return func(user, *args, **kwargs)
? ? ? ? return wrapper
? ? @requires_admin
? ? def delete_user(user, username):
? ? ? ? print(f"刪除用戶 {username}")
4.3 緩存機制
使用 functools.lru_cache 緩存函數(shù)結果:
? ? import functools
? ? @functools.lru_cache(maxsize=128)
? ? def fibonacci(n):
? ? ? ? if n <= 1:
? ? ? ? ? ? return n
? ? ? ? return fibonacci(n-1) + fibonacci(n-2)
? ? print(fibonacci(50))? # 第一次計算較慢,后續(xù)調(diào)用將直接使用緩存結果
4.4 性能測試
統(tǒng)計函數(shù)執(zhí)行時間并生成報告:
? ? import time
? ? import functools
? ? def performance_report(func):
? ? ? ? @functools.wraps(func)
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? start_time = time.perf_counter()
? ? ? ? ? ? result = func(*args, **kwargs)
? ? ? ? ? ? end_time = time.perf_counter()
? ? ? ? ? ? print(f"{func.__name__} 執(zhí)行時間: {end_time - start_time:.6f} 秒")
? ? ? ? ? ? return result
? ? ? ? return wrapper
? ? @performance_report
? ? def process_data(data):
? ? ? ? # 模擬數(shù)據(jù)處理
? ? ? ? time.sleep(1)
? ? ? ? return len(data)
五、裝飾器高級話題
5.1 裝飾器類的參數(shù)化
結合類裝飾器和參數(shù)化裝飾器:
? ? class Retry:
? ? ? ? def __init__(self, max_attempts=3):
? ? ? ? ? ? self.max_attempts = max_attempts
? ? ? ? def __call__(self, func):
? ? ? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? ? ? attempts = 0
? ? ? ? ? ? ? ? while attempts < self.max_attempts:
? ? ? ? ? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ? ? ? ? return func(*args, **kwargs)
? ? ? ? ? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? ? ? ? ? attempts += 1
? ? ? ? ? ? ? ? ? ? ? ? print(f"嘗試 {attempts}/{self.max_attempts} 失敗: {e}")
? ? ? ? ? ? ? ? raise Exception("達到最大重試次數(shù)")
? ? ? ? ? ? return wrapper
? ? @Retry(max_attempts=5)
? ? def connect_to_database():
? ? ? ? # 模擬數(shù)據(jù)庫連接,可能會失敗
? ? ? ? import random
? ? ? ? if random.random() < 0.8:
? ? ? ? ? ? raise ConnectionError("連接失敗")
? ? ? ? return "連接成功"
5.2 裝飾器與異步函數(shù)
異步函數(shù)的裝飾器需要使用 async def 和 await:
? ? import asyncio
? ? import time
? ? def async_timer(func):
? ? ? ? async def wrapper(*args, **kwargs):
? ? ? ? ? ? start_time = time.time()
? ? ? ? ? ? result = await func(*args, **kwargs)
? ? ? ? ? ? end_time = time.time()
? ? ? ? ? ? print(f"異步函數(shù) {func.__name__} 執(zhí)行耗時: {end_time - start_time:.4f} 秒")
? ? ? ? ? ? return result
? ? ? ? return wrapper
? ? @async_timer
? ? async def fetch_data(url):
? ? ? ? await asyncio.sleep(1)? # 模擬網(wǎng)絡請求
? ? ? ? return {"data": "example"}
5.3 裝飾器與類方法
裝飾器也可以應用于類中的方法:
? ? def debug_method(func):
? ? ? ? def wrapper(self, *args, **kwargs):
? ? ? ? ? ? print(f"調(diào)用方法 {func.__name__} 來自實例 {self}")
? ? ? ? ? ? return func(self, *args, **kwargs)
? ? ? ? return wrapper
? ? class MyClass:
? ? ? ? @debug_method
? ? ? ? def method(self, x):
? ? ? ? ? ? return x * 2
六、總結
裝飾器是 Python 中一種非常靈活且強大的工具,它允許我們在不修改原有代碼的情況下增強函數(shù)或類的功能。通過理解裝飾器的基本原理(函數(shù)作為一等公民和閉包),我們可以創(chuàng)建出各種實用的裝飾器,用于日志記錄、權限驗證、緩存、性能測試等場景。
在實際應用中,我們需要注意以下幾點:
? ? 使用 functools.wraps 保留原函數(shù)元信息
? ? 理解裝飾器的執(zhí)行順序(從下到上)
? ? 合理設計帶參數(shù)的裝飾器
? ? 注意異步函數(shù)裝飾器的特殊性
掌握裝飾器是 Python 程序員進階的關鍵一步,它不僅能讓你的代碼更加簡潔優(yōu)雅,還能提高代碼的復用性和可維護性。希望通過本文的介紹,你對 Python 裝飾器有了更深入的理解和掌握。