Python 裝飾器:從入門到精通

一、簡介

在 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 裝飾器有了更深入的理解和掌握。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容