裝飾器: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