Python 具有強大的功能和富有表現力的語法。我最喜歡的裝飾之一。在設計模式的上下文中,裝飾器動態更改方法或類功能,而不必直接使用子類。當您需要擴展功能,但不想修改原函數時,這是理想的選擇。我們可以在任何地方實現裝飾器模式,但是 Python 通過提供更具表現力的功能和語法來促進實現。
在這篇文章中,將討論 Python 的函數裝飾器,并附帶一些澄清有關概念的示例。所有示例均適用 Python 2.7,但相同的概念應適用于Python 3,但語法有所更改。
本質上,裝飾器充當包裝器,在目標函數執行之前和之后修改代碼的行為,而無需修改函數本身,從而增強了原始功能,從而對其進行了裝飾。
您需要了解的功能
在潛水之前,應先弄清一些先決條件。在 Python 中,函數是一等公民,它們是對象,這意味著我們可以用它們做很多有用的事情。
將函數分配給變量
1
2
3
4
5
6
7
|
def greet(name): return "hello "+name greet_someone = greet print(greet_someone("John")) # 輸出: hello John |
在其他函數中定義函數
1
2
3
4
5
6
7
8
9
10
|
def greet(name): def get_message(): return "Hello " result = get_message()+name return result print(greet("John")) # 輸出: Hello John |
可以將函數作為參數傳遞給其他函數
1
2
3
4
5
6
7
8
9
10
|
def greet(name): return "Hello " + name def call_func(func): other_name = "John" return func(other_name) print(call_func(greet)) # 輸出: Hello John |
函數可以返回其他函數
換句話說, 函數生成其他函數。
1
2
3
4
5
6
7
8
9
10
|
def compose_greet_func(): def get_message(): return "Hello there!" return get_message greet = compose_greet_func() print(greet()) # 輸出: Hello there! |
內部函數可以訪問封閉范圍
更通常稱為閉包。在構建裝飾器時會遇到的一種非常強大的模式。還要注意的另一件事是,Python 只允許對外部作用域進行讀取訪問,而不是賦值。請注意,我們如何修改上面的示例以從內部函數的封閉范圍中讀取“name” 參數并返回新函數。
1
2
3
4
5
6
7
8
9
10
|
def compose_greet_func(name): def get_message(): return "Hello there "+name+"!" return get_message greet = compose_greet_func("John") print(greet()) # 輸出: Hello there John! |
裝飾者的組成
函數裝飾器只是現有函數的包裝器。綜上所述,我們可以構建一個裝飾器。在此示例中,我們考慮一個函數,該函數通過p標簽包裝另一個函數的字符串輸出。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) def p_decorate(func): def func_wrapper(name): return "< p >{0}</ p >".format(func(name)) return func_wrapper my_get_text = p_decorate(get_text) print(my_get_text("John")) # 輸出: < p > lorem ipsum, John dolor sit amet</ p > |
那是我們的第一個裝飾。一個將另一個函數作為參數的函數,將生成一個新函數,以擴展原始函數的功能,并返回生成的函數,以便我們可以在任何地方使用它。要讓 get_text 本身由 p_decorate 裝飾,我們只需將 p_decorate 的結果再賦值給 get_text 即可。
1
2
3
4
5
|
get_text = p_decorate(get_text) print(get_text("John")) # 輸出:< p >lorem ipsum, John dolor sit amet</ p > |
還要注意的另一點是,我們的修飾函數帶有一個 name 參數。在裝飾器中我們要做的就是讓 get_text 的包裝傳遞該參數。
Python的裝飾語法
Python通過一些語法糖使創建和使用裝飾器對程序員來說更干凈,更友好。不必裝飾 get_text,get_text = p_decorator(get_text)
它有一個捷徑,即在要使用的函數之前提供裝飾函數的名稱即可。裝飾器的名稱應帶有@
符號。
1
2
3
4
5
6
7
8
9
10
11
12
|
def p_decorate(func): def func_wrapper(name): return "< p >{0}</ p >".format(func(name)) return func_wrapper @p_decorate def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) print(get_text("John")) # 輸出: < p >lorem ipsum, John dolor sit amet</ p > |
現在,讓我們考慮我們要用其他2個函數來修飾 get_text 函數,以便在字符串輸出周圍包裝div和strong標簽。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def p_decorate(func): def func_wrapper(name): return "< p >{0}</ p >".format(func(name)) return func_wrapper def strong_decorate(func): def func_wrapper(name): return "< strong >{0}</ strong >".format(func(name)) return func_wrapper def div_decorate(func): def func_wrapper(name): return "< div >{0}</ div >".format(func(name)) return func_wrapper |
使用基本方法,裝飾 get_text 將遵循以下步驟:
1
|
get_text = div_decorate(p_decorate(strong_decorate(get_text))) |
使用 Python 的裝飾器語法,可以用更具表達力的功能實現相同功能。
1
2
3
4
5
6
7
8
9
|
@div_decorate @p_decorate @strong_decorate def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) print(get_text("John")) # 輸出: < div >< p >< strong >lorem ipsum, John dolor sit amet</ strong ></ p ></ div > |
這里要注意的一件事是設置裝飾器的順序很重要。如果以上示例中的順序不同,則輸出將不同。
裝飾方式
在 Python 中,方法是期望其第一個參數成為對當前對象的引用的函數。我們可以以相同的方式為方法構建裝飾器,同時在包裝函數中考慮自身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def p_decorate(func): def func_wrapper(self): return "< p >{0}</ p >".format(func(self)) return func_wrapper class Person(object): def __init__(self): self.name = "John" self.family = "Doe" @p_decorate def get_fullname(self): return self.name+" "+self.family my_person = Person() print(my_person.get_fullname()) |
更好的方法是使裝飾器對函數和方法都有用。這可以通過將*args 和 **kwargs作為包裝器的參數來完成,然后它可以接受任意數量的參數和關鍵字參數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def p_decorate(func): def func_wrapper(*args, **kwargs): return "< p >{0}</ p >".format(func(*args, **kwargs)) return func_wrapper class Person(object): def __init__(self): self.name = "John" self.family = "Doe" @p_decorate def get_fullname(self): return self.name+" "+self.family my_person = Person() print(my_person.get_fullname()) |
將參數傳遞給裝飾器
回顧上面的示例之前的示例,您會注意到示例中的裝飾器是多么冗余。3個裝飾器(div_decorate,p_decorate,strong_decorate)具有相同的功能,但用不同的標簽包裝字符串。我們絕對可以做得更好。為什么不為將標簽包裝為字符串的標簽提供更通用的實現?是的,請!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def tags(tag_name): def tags_decorator(func): def func_wrapper(name): return "<{0}>{1}</{0}>".format(tag_name, func(name)) return func_wrapper return tags_decorator @tags("p") def get_text(name): return "Hello "+name print(get_text("John")) # 輸出 < p >Hello John</ p > |
在這種情況下,需要做更多的工作。裝飾器期望接收一個函數作為參數,這就是為什么我們必須構建一個接受這些額外參數并動態生成裝飾器的原因。在上面的示例tags,是我們的裝飾器生成器。
調試裝飾功能
歸根結底,裝飾器只是包裝我們的函數,以防調試出現問題,因為包裝器函數不攜帶原始函數的名稱,模塊和文檔字符串。基于上面的示例,如果我們這樣做:
1
2
|
print(get_text.__name__) # 輸出 func_wrapper |
期待輸出get_text,然而,get_text的__name__
,__doc__
和__module__
屬性被包裝(func_wrapper)覆蓋。顯然,我們可以在func_wrapper中重置它們,但是Python提供了一種更好的方法。
救援工具
幸運的是,Python(從版本2.5開始)包括functools模塊,其中包含functools.wraps。Wraps 是一個修飾器,用于將包裝函數(func_wrapper)的屬性更新為原始函數(get_text)的屬性。這就像通過@wraps(func)裝飾func_wrapper一樣簡單。這是更新的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from functools import wraps def tags(tag_name): def tags_decorator(func): @wraps(func) def func_wrapper(name): return "<{0}>{1}</{0}>".format(tag_name, func(name)) return func_wrapper return tags_decorator @tags("p") def get_text(name): """returns some text""" return "Hello "+name print(get_text.__name__) # get_text print(get_text.__doc__) # returns some text print(get_text.__module__) # __main__ |
您可以從輸出中注意到,get_text 的屬性現在是正確的屬性。
裝飾器在哪里使用
相對于您可以使用裝飾器完成的工作量,本文中的示例非常簡單。它們可以為您的程序提供如此強大的功能。通常,裝飾器是擴展我們不想修改的函數的行為的理想選擇。有關有用的裝飾器的大量清單,建議您查看Python Decorator Library