Python中的裝飾器

深藍小佛發表於2022-03-26

裝飾器:Decorators

Python裝飾器接收一個函式物件,新增一些功能,並返回該函式物件。在本文中,你將瞭解如何建立裝飾器(decorator)以及為什麼要使用它。

Python 有一個有趣的特性叫做裝飾器,可以在不改動現有程式碼的情況下新增新功能。這也稱為超程式設計(metaprogramming),因為程式的一部分試圖在編譯時修改程式的另一部分。

先決條件

為了理解裝飾器,我們必須首先了解 Python 中的一些基本知識。

我們必須接受這樣一個事實,即 Python 中的所有內容(即使是類) 都是物件,一切皆物件。我們定義的名稱只是繫結到這些物件的識別符號。函式也不例外,它們也是物件(帶有屬性)。

  • 可以將各種不同的名稱繫結到同一個函式物件

舉個例子:

# 定義一個普通函式
def first(msg):
    print(msg)

first("乾飯人")   # 呼叫first函式
second = first   # 將first函式賦值給second變數
second("乾飯人")  # 呼叫second函式

輸出:

乾飯人
乾飯人

當您執行程式碼時,第一個和第二個函式都給出相同的輸出。這裡,第一個和第二個名稱指的是同一個函式物件。

  • 函式可以作為引數傳遞給另一個函式

現在事情開始變得有點意思了。

如果你在 Python 中使用過 map()filter()reduce() 等函式,那麼你肯定已經是老司機了。將其他函式作為引數的函式也稱為高階函式higher order functions)。

舉個例子:

def add(x):
    return x + 1


def lose(x):
    return x - 1


def operate(func, x):
    result = func(x)
    return result

我們按照以下方式呼叫該函式。

operate(add,3)  # 輸出:4
operate(lose,3)  # 輸出:2
  • 一個函式可以返回另一個函式。

既然函式是物件,那麼你都可以作為其他函式的入參了,把你作為高階函式的返回值應該不過分吧。

舉個例子:

def is_called():
    def is_returned():
        print("乾飯人")
    return is_returned


new = is_called()
new()

這裡,is_returned()是一個巢狀函式,每次呼叫 is _called()時定義並返回該函式。


輸出:

乾飯人

  • 閉包

最後,我們必須瞭解 Python 中的閉包Closure),這裡不贅述了,敬請移步至小主相關博文。


裝飾器

函式和方法被稱為可呼叫物件callable),因為它們可以被呼叫。

實際上,任何實現魔法方法 __call__()的物件都稱為可呼叫的(callable)。因此,我們可以得出一個結論:裝飾器是一個可呼叫物件,它用來返回一個可呼叫物件。


A decorator is a callable that returns a callable.


通常,裝飾器會接受一個函式,新增一些功能,最後返回該函式。

舉個例子:

# 宣告第一個函式make_pretty
def make_pretty(func):
    def inner():
        print("我是make_pretty,我來搞點事情")
        func()
    return inner

# 宣告第二個函式ordinary
def ordinary():
    print("我是ordinary,我的內心毫無波瀾")

如果我們執行程式碼:

ordinary()

輸出:

我是ordinary,我的內心毫無波瀾

如果我們執行程式碼:

pretty = make_pretty(ordinary)
pretty()

則會輸出:

我是make_pretty,我來搞點事情
我是ordinary,我的內心毫無波瀾

在上面的例子中,pretty = make_pretty(ordinary) 表明 make_pretty()是一個裝飾器,ordinary函式被裝飾,返回的函式指定名稱為pretty

我們可以看到 decorator 函式向原始函式新增了一些新功能。這類似於包裝禮物。Decorator 充當包裝器。被裝飾的物件的性質(裡面的實際禮物)不會改變。但是現在,它看起來很漂亮(因為它被裝飾過)。

通常,我們將函式裝飾為:ordinary = make_pretty(ordinary)

這是一個常見的構造,出於這個原因,Python 使用了一種語法來簡化它。

我們可以將 @ 符號與裝飾器函式的名稱一起使用,並將其置於要裝飾的函式的定義之上。這只是實現裝飾器的一個語法糖。

舉個例子:

@make_pretty
def ordinary():
    print("I am ordinary")

效果相當於:

def ordinary():
    print("I am ordinary")
ordinary = make_pretty(ordinary)

裝飾器引數

前面演示的裝飾器很簡單,它只處理沒有任何引數的函式。

如果我們有這樣的函式:

def divide(a, b):
    return a/b

divide(2,5)  # 輸出:0.4
divide(2,0)  # 報錯 “ZeroDivisionError: division by zero”

這個函式有兩個引數 a 和 b。我們知道如果我們把分母 b 傳入0,將會導致ZeroDivisionError錯誤。

現在讓我們建立一個裝飾器來檢查這個會導致錯誤的情況。

優化後:

def smart_divide(func):
    def inner(a, b):
        print(f"將要計算 {a} 除以 {b}")
        if b == 0:
            print("分母不能為0!")
            return

        return func(a, b)
    return inner


@smart_divide
def divide(a, b):
    print(a/b)

解釋:如果出現錯誤條件,這個新實現將返回 None。

如果執行:

print(divide(2,5))

輸出:

將要計算 2 除以 5
0.4

如果執行:

print(divide(2,0))

輸出:

將要計算 2 除以 0
分母不能為0!
None

通過這種方式,我們可以裝飾帶有引數的函式。

細心的朋友會注意到,裝飾器 smart_divide 中巢狀的 inner() 函式的引數與它所裝飾的函式的引數是相同的。

考慮到這一點,現在我們可以使通用裝飾符與任意數量的引數一起工作。

在 Python 中,可以寫成這樣:function(*args, **kwargs)。其中,args 表示位置引數組成的元組,而 kwargs 表示關鍵字引數組成的字典。其中*號和**號表示引數個數不限。

舉個例子:

def works_for_all(func):
    def inner(*args, **kwargs):
        print("I can decorate any function")
        return func(*args, **kwargs)
    return inner

裝飾器巢狀

在python中,裝飾器可以巢狀使用,有點像套娃。也就是說,一個函式可以使用不同(或相同)的裝飾器進行多次裝飾。我們只需簡單地將裝飾器置於期望的函式之上即可實現。

舉個例子:

def star(func):
    def inner(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner


def percent(func):
    def inner(*args, **kwargs):
        print("%" * 30)
        func(*args, **kwargs)
        print("%" * 30)
    return inner


@star
@percent
def show(msg):
    print(msg)


show("我是宇宙中心")

上面的雙迭代器的效果相當於:

def printer(msg):
    print(msg)
printer = star(percent(printer))

輸出:

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
我是宇宙中心
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

需要注意的是,我們裝飾鏈的順序很重要。

如果我們顛倒了順序:

@percent
@star
def printer(msg):
    print(msg)

輸出結果將是:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
我是宇宙中心
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



好的,以上就是關於Python裝飾器的全部內容了。喜歡本文的小夥伴記得三連哦~

---END

相關文章