python 裝飾器


Python 中的裝飾器(Decorators) 是一種強(qiáng)大且優(yōu)雅的語法糖,它允許你在不修改原始函數(shù)或類定義的情況下,動(dòng)態(tài)地給它們添加功能或修改它們的行為。你可以將裝飾器想象成一個(gè)“包裝器”,它在不改變被包裝對(duì)象內(nèi)容的前提下,為其增加新的特性。


什么是裝飾器?

從本質(zhì)上講,裝飾器是一個(gè)接受函數(shù)(或類)作為輸入,并返回一個(gè)新函數(shù)(或新類)的可調(diào)用對(duì)象(callable)。這個(gè)新函數(shù)/類通常會(huì)包含原始函數(shù)/類的邏輯,并在此基礎(chǔ)上添加一些額外的功能。

Python 中使用 @ 符號(hào)來應(yīng)用裝飾器,它放在函數(shù)或類定義的上方。

# 定義一個(gè)裝飾器函數(shù)
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func() # 調(diào)用原始函數(shù)
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator # 使用 @ 語法糖應(yīng)用裝飾器
def say_hello():
    print("Hello!")

say_hello()

輸出:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

在這個(gè)例子中,my_decorator 就是一個(gè)裝飾器。@my_decorator 放在 say_hello 函數(shù)上方,等同于執(zhí)行了 say_hello = my_decorator(say_hello)

裝飾器的工作原理

  1. 定義裝飾器:你首先定義一個(gè)函數(shù)(或類,更高級(jí)的用法),它接受一個(gè)函數(shù)作為參數(shù)。
  2. 定義內(nèi)部函數(shù)(Wrapper):在這個(gè)裝飾器內(nèi)部,你通常會(huì)定義一個(gè)嵌套函數(shù)(通常命名為 wrapper),這個(gè) wrapper 函數(shù)會(huì)包含你想要添加的額外邏輯,并且會(huì)在某個(gè)地方調(diào)用原始函數(shù)。
  3. 返回 Wrapper:裝飾器函數(shù)最后返回這個(gè) wrapper 函數(shù)。
  4. 應(yīng)用裝飾器:當(dāng)你使用 @decorator_name 語法時(shí),Python 會(huì)自動(dòng)將被裝飾的函數(shù)作為參數(shù)傳遞給 decorator_name 函數(shù),然后用返回的 wrapper 函數(shù)替換掉原始函數(shù)。

為什么使用裝飾器?

裝飾器提供了一種優(yōu)雅的方式來實(shí)現(xiàn)以下目標(biāo):

  1. 代碼重用:將通用的功能(如日志、性能計(jì)時(shí)、權(quán)限檢查、緩存等)封裝成裝飾器,可以在多個(gè)函數(shù)或類中重復(fù)使用,避免代碼重復(fù)。
  2. 關(guān)注點(diǎn)分離:將核心業(yè)務(wù)邏輯與非核心的橫切關(guān)注點(diǎn)(cross-cutting concerns)分離。例如,一個(gè)函數(shù)只負(fù)責(zé)其核心計(jì)算,而日志記錄則由裝飾器處理。
  3. 提高可讀性:通過簡潔的 @ 語法,代碼的意圖更加清晰。
  4. 不修改原始代碼:你可以在不觸碰原始函數(shù)或類定義的情況下,擴(kuò)展或修改其行為。

裝飾器的常見應(yīng)用場(chǎng)景

  • 日志記錄(Logging):記錄函數(shù)調(diào)用的時(shí)間、參數(shù)和返回值。
  • 性能分析(Profiling):測(cè)量函數(shù)的執(zhí)行時(shí)間。
  • 權(quán)限檢查(Authorization):驗(yàn)證用戶是否有權(quán)訪問某個(gè)函數(shù)或資源。
  • 緩存(Caching):緩存函數(shù)的返回結(jié)果,避免重復(fù)計(jì)算。
  • 輸入驗(yàn)證(Input Validation):在函數(shù)執(zhí)行前檢查輸入?yún)?shù)的有效性。
  • 重試機(jī)制(Retries):當(dāng)函數(shù)失敗時(shí)自動(dòng)重試。
  • API 路由(API Routing):在 Web 框架中,如 Flask 和 Django,裝飾器常用于將 URL 路徑映射到視圖函數(shù)。

帶參數(shù)的函數(shù)裝飾器

如果被裝飾的函數(shù)需要參數(shù),你的 wrapper 函數(shù)也必須能接收這些參數(shù),并傳遞給原始函數(shù)。這通常通過 *args**kwargs 來實(shí)現(xiàn)。

def log_arguments(func):
    def wrapper(*args, **kwargs): # 接收任意位置參數(shù)和關(guān)鍵字參數(shù)
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

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

@log_arguments
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

add(10, 20)
greet("Alice", greeting="Hi")

帶參數(shù)的裝飾器

有些時(shí)候,你可能需要自定義裝飾器的行為,這就需要裝飾器本身接收參數(shù)。這通常通過一個(gè)三層嵌套的結(jié)構(gòu)來實(shí)現(xiàn)。外層函數(shù)接收裝飾器參數(shù),然后返回真正的裝飾器函數(shù)。

def repeat(num_times): # 第一層:接收裝飾器參數(shù)
    def decorator(func): # 第二層:接收被裝飾的函數(shù)
        def wrapper(*args, **kwargs): # 第三層:接收被裝飾函數(shù)的參數(shù)
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(num_times=3) # 調(diào)用裝飾器,傳入?yún)?shù)
def bark():
    print("Woof!")

@repeat(num_times=2)
def greet_person(name):
    print(f"Hello, {name}!")

bark()
print("---")
greet_person("Bob")

類裝飾器

裝飾器不僅可以裝飾函數(shù),還可以裝飾類。此外,你也可以使用類來作為裝飾器,如果這個(gè)類實(shí)現(xiàn)了 __call__ 方法。

# 類作為裝飾器
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs): # 使類的實(shí)例可調(diào)用
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hi():
    print("Hi!")

say_hi()
say_hi()

@functools.wraps

在使用裝飾器時(shí),一個(gè)常見的問題是,裝飾器會(huì)替換掉原始函數(shù)的元數(shù)據(jù)(如 __name__、__doc__)。為了解決這個(gè)問題,Python 提供了 functools.wraps 裝飾器。

import functools

def my_logging_decorator(func):
    @functools.wraps(func) # 使用 wraps 裝飾器
    def wrapper(*args, **kwargs):
        """This is the wrapper function's docstring."""
        print(f"Logging call to {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@my_logging_decorator
def my_function():
    """This is my_function's docstring."""
    pass

print(my_function.__name__) # 輸出: my_function
print(my_function.__doc__)  # 輸出: This is my_function's docstring.

如果沒有 @functools.wraps(func),my_function.__name__ 會(huì)是 wrappermy_function.__doc__ 會(huì)是 wrapper 函數(shù)的文檔字符串。


裝飾器是 Python 中一個(gè)非常強(qiáng)大且常用的高級(jí)特性,理解它對(duì)于編寫簡潔、可維護(hù)和可擴(kuò)展的代碼至關(guān)重要。

你對(duì)裝飾器還有什么具體的問題嗎?

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

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

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