Python 具有強(qiáng)大的功能和富有表現(xiàn)力的語法。我最喜歡的裝飾之一。在設(shè)計(jì)模式的上下文中,裝飾器動(dòng)態(tài)更改方法或類功能,而不必直接使用子類。當(dāng)您需要擴(kuò)展功能,但不想修改原函數(shù)時(shí),這是理想的選擇。我們可以在任何地方實(shí)現(xiàn)裝飾器模式,但是 Python 通過提供更具表現(xiàn)力的功能和語法來促進(jìn)實(shí)現(xiàn)。
在這篇文章中,將討論 Python 的函數(shù)裝飾器,并附帶一些澄清有關(guān)概念的示例。所有示例均適用 Python 2.7,但相同的概念應(yīng)適用于Python 3,但語法有所更改。
本質(zhì)上,裝飾器充當(dāng)包裝器,在目標(biāo)函數(shù)執(zhí)行之前和之后修改代碼的行為,而無需修改函數(shù)本身,從而增強(qiáng)了原始功能,從而對(duì)其進(jìn)行了裝飾。
您需要了解的功能
在潛水之前,應(yīng)先弄清一些先決條件。在 Python 中,函數(shù)是一等公民,它們是對(duì)象,這意味著我們可以用它們做很多有用的事情。
將函數(shù)分配給變量
1
2
3
4
5
6
7
|
def greet(name): return "hello "+name greet_someone = greet print(greet_someone("John")) # 輸出: hello John |
在其他函數(shù)中定義函數(shù)
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 |
可以將函數(shù)作為參數(shù)傳遞給其他函數(shù)
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 |
函數(shù)可以返回其他函數(shù)
換句話說, 函數(shù)生成其他函數(shù)。
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! |
內(nèi)部函數(shù)可以訪問封閉范圍
更通常稱為閉包。在構(gòu)建裝飾器時(shí)會(huì)遇到的一種非常強(qiáng)大的模式。還要注意的另一件事是,Python 只允許對(duì)外部作用域進(jìn)行讀取訪問,而不是賦值。請(qǐng)注意,我們?nèi)绾涡薷纳厦娴氖纠詮膬?nèi)部函數(shù)的封閉范圍中讀取“name” 參數(shù)并返回新函數(shù)。
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! |
裝飾者的組成
函數(shù)裝飾器只是現(xiàn)有函數(shù)的包裝器。綜上所述,我們可以構(gòu)建一個(gè)裝飾器。在此示例中,我們考慮一個(gè)函數(shù),該函數(shù)通過p標(biāo)簽包裝另一個(gè)函數(shù)的字符串輸出。
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 > |
那是我們的第一個(gè)裝飾。一個(gè)將另一個(gè)函數(shù)作為參數(shù)的函數(shù),將生成一個(gè)新函數(shù),以擴(kuò)展原始函數(shù)的功能,并返回生成的函數(shù),以便我們可以在任何地方使用它。要讓 get_text 本身由 p_decorate 裝飾,我們只需將 p_decorate 的結(jié)果再賦值給 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 > |
還要注意的另一點(diǎn)是,我們的修飾函數(shù)帶有一個(gè) name 參數(shù)。在裝飾器中我們要做的就是讓 get_text 的包裝傳遞該參數(shù)。
Python的裝飾語法
Python通過一些語法糖使創(chuàng)建和使用裝飾器對(duì)程序員來說更干凈,更友好。不必裝飾 get_text,get_text = p_decorator(get_text)
它有一個(gè)捷徑,即在要使用的函數(shù)之前提供裝飾函數(shù)的名稱即可。裝飾器的名稱應(yīng)帶有@
符號(hào)。
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 > |
現(xiàn)在,讓我們考慮我們要用其他2個(gè)函數(shù)來修飾 get_text 函數(shù),以便在字符串輸出周圍包裝div和strong標(biāo)簽。
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 的裝飾器語法,可以用更具表達(dá)力的功能實(shí)現(xiàn)相同功能。
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 > |
這里要注意的一件事是設(shè)置裝飾器的順序很重要。如果以上示例中的順序不同,則輸出將不同。
裝飾方式
在 Python 中,方法是期望其第一個(gè)參數(shù)成為對(duì)當(dāng)前對(duì)象的引用的函數(shù)。我們可以以相同的方式為方法構(gòu)建裝飾器,同時(shí)在包裝函數(shù)中考慮自身。
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()) |
更好的方法是使裝飾器對(duì)函數(shù)和方法都有用。這可以通過將*args 和 **kwargs作為包裝器的參數(shù)來完成,然后它可以接受任意數(shù)量的參數(shù)和關(guān)鍵字參數(shù)。
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()) |
將參數(shù)傳遞給裝飾器
回顧上面的示例之前的示例,您會(huì)注意到示例中的裝飾器是多么冗余。3個(gè)裝飾器(div_decorate,p_decorate,strong_decorate)具有相同的功能,但用不同的標(biāo)簽包裝字符串。我們絕對(duì)可以做得更好。為什么不為將標(biāo)簽包裝為字符串的標(biāo)簽提供更通用的實(shí)現(xiàn)?是的,請(qǐng)!
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 > |
在這種情況下,需要做更多的工作。裝飾器期望接收一個(gè)函數(shù)作為參數(shù),這就是為什么我們必須構(gòu)建一個(gè)接受這些額外參數(shù)并動(dòng)態(tài)生成裝飾器的原因。在上面的示例tags,是我們的裝飾器生成器。
調(diào)試裝飾功能
歸根結(jié)底,裝飾器只是包裝我們的函數(shù),以防調(diào)試出現(xiàn)問題,因?yàn)榘b器函數(shù)不攜帶原始函數(shù)的名稱,模塊和文檔字符串。基于上面的示例,如果我們這樣做:
1
2
|
print(get_text.__name__) # 輸出 func_wrapper |
期待輸出get_text,然而,get_text的__name__
,__doc__
和__module__
屬性被包裝(func_wrapper)覆蓋。顯然,我們可以在func_wrapper中重置它們,但是Python提供了一種更好的方法。
救援工具
幸運(yùn)的是,Python(從版本2.5開始)包括functools模塊,其中包含functools.wraps。Wraps 是一個(gè)修飾器,用于將包裝函數(shù)(func_wrapper)的屬性更新為原始函數(shù)(get_text)的屬性。這就像通過@wraps(func)裝飾func_wrapper一樣簡(jiǎn)單。這是更新的示例:
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 的屬性現(xiàn)在是正確的屬性。
裝飾器在哪里使用
相對(duì)于您可以使用裝飾器完成的工作量,本文中的示例非常簡(jiǎn)單。它們可以為您的程序提供如此強(qiáng)大的功能。通常,裝飾器是擴(kuò)展我們不想修改的函數(shù)的行為的理想選擇。有關(guān)有用的裝飾器的大量清單,建議您查看Python Decorator Library