首頁 > 後端開發 > Python教學 > Python 裝飾器:綜合指南

Python 裝飾器:綜合指南

Barbara Streisand
發布: 2025-01-05 22:53:43
原創
795 人瀏覽過

Python Decorators: A Comprehensive Guide

開始使用Python程式設計時,如果沒記錯的話,版本是3.3。因此,當我開始編程時,Python 社群很早就可以使用裝飾器了。

Python 2.2 版本引入了函數裝飾器,Python 2.6 版本引入了類別裝飾器。

就我個人而言,我認為 Python 的裝飾器功能是該語言的一個非常強大的功能。

實際上,我的目標是製作一系列有關 Python 中最難理解的主題的文章。我打算一一涵蓋這些主題,大概有十多個。

在本文中,我將嘗試盡可能多地觸及裝飾器主題的每個部分。

1. 歷史背景

  • 早期(Python 2.2 之前): 在裝飾器之前,修改函數或類別通常需要手動包裝或猴子修補,這很麻煩且可讀性較差。
  • 元類別 (Python 2.2): 元類別提供了一種控制類別創建的方法,提供了裝飾器稍後提供的一些功能,但對於簡單的修改來說它們很複雜。
  • PEP 318 (Python 2.4): 裝飾器透過 PEP 318 在 Python 2.4 中正式引入。該提案受到 Java 中註釋的啟發,旨在提供一種更清晰、更具聲明性的方式來修改函數和方法.
  • 類別裝飾器 (Python 2.6): Python 2.6 擴展了對類別的裝飾器支持,進一步增強了類別的多功能性。
  • 廣泛採用: 裝飾器很快就成為一種流行的功能,廣泛用於 Flask 和 Django 等框架中,用於路由、驗證等。

2.什麼是裝飾器?

本質上,裝飾器是Python中的一種設計模式,它允許您修改函數或類別的行為而不改變其核心結構。裝飾器是元程式設計的一種形式,您本質上是在編寫操作其他程式碼的程式碼。

您知道 Python 使用以下順序給出的範圍來解析名稱:

  1. 本地
  2. 附上
  3. 全球
  4. 內建

裝飾器是坐封閉作用域,這與閉包概念密切相關。

關鍵思想:裝飾器將函數作為輸入,向其添加一些功能,然後返回修改後的函數。

類比:將裝飾器視為禮品包裝紙。您有一件禮物(原始功能),然後用裝飾紙(裝飾器)將其包裹起來,使其看起來更漂亮或添加額外的功能(例如蝴蝶結或卡片)。裡面的禮物保持不變,但它的呈現或相關動作得到了增強。

A) 裝飾器變體:基於函數與基於類

Python 中的大多數裝飾器都是使用函數實現的,但您也可以使用類別建立裝飾器。
基於函數的裝飾器更常見、更簡單,而基於類別的裝飾器提供了額外的靈活性。

基於函數的基本裝飾器語法

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
登入後複製
登入後複製

說明:

  • my_decorator 是裝飾器函數。它需要將函數 func 修飾為輸入。
  • 包裝器是包裝原始函數呼叫的內部函數。它可以在原始函數之前和之後執行程式碼。
  • @my_decorator 是裝飾器語法。相當於 say_hello = my_decorator(say_hello).

基於類別的基本裝飾器語法

它們使用類別而不是函數來定義裝飾器。

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = self.func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result

@MyDecorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
登入後複製
登入後複製

說明:

  • MyDecorator 是一個充當裝飾器的類別。
  • __init__方法儲存要裝飾的函數。
  • __call__ 方法使類別實例可調用,允許它像函數一樣使用。

B) 實作一個簡單的裝飾器

裝飾器的基本概念是它們是將另一個函數作為參數並擴展其行為而不明確修改它的函數。
這是最簡單的形式:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

# Using the decorator with @ syntax
@my_decorator
def say_hello():
    print("Hello!")

# When we call say_hello()
say_hello()

# This is equivalent to:
# say_hello = my_decorator(say_hello)
登入後複製
登入後複製

C) 實作有參數的裝飾器

讓我們建立一個記錄函數執行時間的裝飾器:

def decorator_with_args(func):
    def wrapper(*args, **kwargs):    # Accept any number of arguments
        print(f"Arguments received: {args}, {kwargs}")
        return func(*args, **kwargs)  # Pass arguments to the original function
    return wrapper

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

greet("Alice", greeting="Hi")  # Prints arguments then "Hi, Alice!"
登入後複製
登入後複製

D) 實現參數化裝飾器

這些裝飾器可以接受自己的參數:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello {name}")
    return "Done"

greet("Bob")  # Prints "Hello Bob" three times
登入後複製

E) 實作類別裝飾器

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("Initializing database connection")

# Creating multiple instances actually returns the same instance
db1 = DatabaseConnection()  # Prints initialization
db2 = DatabaseConnection()  # No initialization printed
print(db1 is db2)  # True
登入後複製

F) 實作方法裝飾器

這些是專門為類別方法設計的:

def debug_method(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling method {func.__name__} of {self.__class__.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @debug_method
    def my_method(self, x, y):
        return x + y

obj = MyClass()
print(obj.my_method(5, 3))

登入後複製

G) 實現裝飾器鏈接

多個裝飾器可以應用於單一函數:

def bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def greet():
    return "Hello!"

print(greet())  # Outputs: <b><i>Hello!</i></b>
登入後複製

說明:

  • 裝飾器是從下到上應用的。
  • 這更像是我們在數學中所做的:f(g(x))。
  • 先塗抹斜體,然後再應用粗體。

H)如果我們不使用 @functools.wraps 會發生什麼事?

functools.wraps 裝飾器(請參閱文件)是一個輔助函數,當您使用裝飾器包裝原始函數時,它會保留原始函數的元資料(如其名稱、文件字串和簽名)。如果您不使用它,您將丟失這些重要資訊。

範例:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper docstring"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def my_function():
    """My function docstring"""
    pass

print(my_function.__name__)
print(my_function.__doc__)
登入後複製

輸出:

wrapper
Wrapper docstring
登入後複製

問題:

  • 原始函數的名稱(my_function)和文件字串(「我的函數文件字串」)遺失。
  • 這會使調試和自省變得困難。

解:使用 functools.wraps):

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
登入後複製
登入後複製

輸出:

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = self.func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result

@MyDecorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
登入後複製
登入後複製

functools.wraps 的好處:

  • 保留函數元資料。
  • 提高程式碼可讀性和可維護性。
  • 讓除錯更容易。
  • 幫助使用內省工具和文件產生器。

I) 具有狀態的裝飾器

裝飾器也可以維護函數呼叫之間的狀態。這對於緩存或計算函數呼叫等場景特別有用。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

# Using the decorator with @ syntax
@my_decorator
def say_hello():
    print("Hello!")

# When we call say_hello()
say_hello()

# This is equivalent to:
# say_hello = my_decorator(say_hello)
登入後複製
登入後複製

輸出:

def decorator_with_args(func):
    def wrapper(*args, **kwargs):    # Accept any number of arguments
        print(f"Arguments received: {args}, {kwargs}")
        return func(*args, **kwargs)  # Pass arguments to the original function
    return wrapper

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

greet("Alice", greeting="Hi")  # Prints arguments then "Hi, Alice!"
登入後複製
登入後複製

說明:

包裝函數維護一個計數器(呼叫),每次呼叫裝飾函數時該計數器都會增加。
這是一個如何使用裝飾器來維護狀態的簡單範例。

J) Python 裝飾器的最佳實踐

  • 使用 functools.wraps: 始終在裝飾器中使用 @functools.wraps 保留原始函數的元資料。
  • 保持裝飾器簡單: 理想情況下,裝飾器應該做一件特定的事情並且做得很好。這使得它們更可重複使用且更易於理解。
  • 記錄你的裝飾器:解釋你的裝飾器做了什麼,它需要什麼參數,以及它回傳什麼。
  • 測試你的裝飾器:編寫單元測試以確保你的裝飾器在各種場景下按預期工作。
  • 考慮連結順序:連結多個裝飾器時請注意順序,因為它會影響執行流程。

K) 糟糕的實現(反模式)

  • 過於複雜的裝飾器: 避免創建過於複雜或嘗試做太多事情的裝飾器。這使得它們難以理解、維護和調試。
  • 忽略 functools.wraps: 忘記使用 @functools.wraps 會導致函數元資料遺失,進而導致自省和偵錯問題。
  • 副作用: 理想情況下,裝飾器除了修改裝飾函數之外不應產生意外的副作用。
  • 硬編碼值: 避免在裝飾器中對值進行硬編碼。相反,使用裝飾器工廠來使其可配置。
  • 未正確處理參數: 如果裝飾器要與各種函數一起使用,請確保您的包裝函數可以使用 *args 和 **kwargs 處理任意數量的位置和關鍵字參數。

L) 10. 現實世界用例

  • 日誌記錄: 記錄函數呼叫、參數和傳回值以進行偵錯或審核。
  • 計時: 測量函數的執行時間以進行效能分析。
  • 快取: 儲存昂貴的函數呼叫的結果以避免冗餘計算(記憶)。
  • 驗證與授權: 在執行函數之前驗證使用者憑證或權限。
  • 輸入驗證: 檢查傳遞給函數的參數是否符合特定條件。
  • 速率限制: 控制在特定時間內呼叫函數的次數。
  • 重試邏輯: 如果由於暫存錯誤而失敗,則自動重試函數呼叫。
  • 特定於框架的任務: Flask 和 Django 等框架使用裝飾器進行路由(將 URL 映射到函數)、註冊插件等。

M) Python 裝飾器精選列表

您可以在下面找到 Python 裝飾器的精選清單:

  • 很棒的 Python 裝飾器
  • Python 裝飾器庫

N) 11. 結論

裝飾器是 Python 中一個強大而優雅的功能,它允許您以乾淨和聲明性的方式增強函數和類別。
透過了解原理、最佳實踐和潛在陷阱,您可以有效地利用裝飾器來編寫更模組化、可維護和富有表現力的程式碼。
它們是任何 Python 程式設計師的武器庫中的寶貴工具,特別是在使用框架或建立可重複使用元件時。

以上是Python 裝飾器:綜合指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板