您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python裝飾器怎么應用”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Python裝飾器怎么應用”文章能幫助大家解決問題。
Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名前面加一個@XXX注解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很讓人望而卻步,在使用它之前你需要了解一堆Annotation的類庫文檔,讓人感覺就是在學另外一門語言。而Python使用了一種相對于Decorator Pattern和Annotation來說非常優雅的方法,這種方法不需要你去掌握什么復雜的OO模型或是Annotation的各種類庫規定,完全就是語言層面的玩法:一種函數式編程的技巧。
在Python中,裝飾器實現是十分方便。原因是:函數可以被扔來扔去。
要理解裝飾器,就必須先知道,在Python里,函數也是對象(functions are objects)。明白這一點非常重要,讓我們通過一個例子來看看為什么。
def shout(word="yes"): **return** word.capitalize() + "!" print(shout()) # outputs : 'Yes!' # 作為一個對象,你可以像其他對象一樣把函數賦值給其他變量 scream = shout # 注意我們沒有用括號:我們不是在調用函數, # 而是把函數'shout'的值綁定到'scream'這個變量上 # 這也意味著你可以通過'scream'這個變量來調用'shout'函數 print(scream()) # outputs : 'Yes!' # 不僅如此,這也還意味著你可以把原來的名字'shout'刪掉, # 而這個函數仍然可以通過'scream'來訪問 del shout **try**: print(shout()) **except** NameError as e: print(e) # outputs: "name 'shout' is not defined" print(scream()) # outputs: 'Yes!'
Python 函數的另一個有趣的特性是,它們可以在另一個函數體內定義。
def talk(): # 你可以在 'talk' 里動態的(on the fly)定義一個函數... **def** whisper(word="yes"): **return** word.lower() + "..." # ... 然后馬上調用它! print(whisper()) # 每當調用'talk',都會定義一次'whisper',然后'whisper'在'talk'里被調用 talk() # outputs: # "yes..." # 但是"whisper" 在 "talk"外并不存在: **try**: print(whisper()) **except** NameError as e: print(e) # outputs : "name 'whisper' is not defined"
你剛剛已經知道了,Python的函數也是對象,因此:
可以被賦值給變量
可以在另一個函數體內定義
那么,這樣就意味著一個函數可以返回另一個函數:
def get_talk(type="shout"): # 我們先動態定義一些函數 **def** shout(word="yes"): **return** word.capitalize() + "!" **def** whisper(word="yes"): **return** word.lower() + "..." # 然后返回其中一個 **if** type == "shout": # 注意:我們是在返回函數對象,而不是調用函數,所以不要用到括號 "()" **return** shout **else**: **return** whisper # 那你改如何使用d呢? # 先把函數賦值給一個變量 talk = get_talk() # 你可以發現 "talk" 其實是一個函數對象: print(talk) # outputs :# 這個對象就是 get_talk 函數返回的: print(talk()) # outputs : Yes! # 你甚至還可以直接這樣使用: print(get_talk("whisper")()) # outputs : yes...
既然可以返回一個函數,那么也就可以像參數一樣傳遞:
def shout(word="yes"): **return** word.capitalize() + "!" scream = shout **def** do_something_before(func): print("I do something before then I call the function you gave me") print(func()) do_something_before(scream) # outputs: # I do something before then I call the function you gave me # Yes!
現在已經具備了理解裝飾器的所有基礎知識了。裝飾器也就是一種包裝材料,它們可以讓你在執行被裝飾的函數之前或之后執行其他代碼,而且不需要修改函數本身。
# 一個裝飾器是一個需要另一個函數作為參數的函數 **def** my_shiny_new_decorator(a_function_to_decorate): # 在裝飾器內部動態定義一個函數:wrapper(原意:包裝紙). # 這個函數將被包裝在原始函數的四周 # 因此就可以在原始函數之前和之后執行一些代碼. **def** the_wrapper_around_the_original_function(): # 把想要在調用原始函數前運行的代碼放這里 print("Before the function runs") # 調用原始函數(需要帶括號) a_function_to_decorate() # 把想要在調用原始函數后運行的代碼放這里 print("After the function runs") # 直到現在,"a_function_to_decorate"還沒有執行過 (HAS NEVER BEEN EXECUTED). # 我們把剛剛創建的 wrapper 函數返回. # wrapper 函數包含了這個函數,還有一些需要提前后之后執行的代碼, # 可以直接使用了(It's ready to use!) **return** the_wrapper_around_the_original_function # Now imagine you create a function you don't want to ever touch again. **def** a_stand_alone_function(): print("I am a stand alone function, don't you dare modify me") a_stand_alone_function() # outputs: I am a stand alone function, don't you dare modify me # 現在,你可以裝飾一下來修改它的行為. # 只要簡單的把它傳遞給裝飾器,后者能用任何你想要的代碼動態的包裝 # 而且返回一個可以直接使用的新函數: a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() # outputs: # Before the function runs # I am a stand alone function, don't you dare modify me # After the function runs
我們用裝飾器的語法來重寫一下前面的例子:
# 一個裝飾器是一個需要另一個函數作為參數的函數 **def** my_shiny_new_decorator(a_function_to_decorate): # 在裝飾器內部動態定義一個函數:wrapper(原意:包裝紙). # 這個函數將被包裝在原始函數的四周 # 因此就可以在原始函數之前和之后執行一些代碼. **def** the_wrapper_around_the_original_function(): # 把想要在調用原始函數前運行的代碼放這里 print("Before the function runs") # 調用原始函數(需要帶括號) a_function_to_decorate() # 把想要在調用原始函數后運行的代碼放這里 print("After the function runs") # 直到現在,"a_function_to_decorate"還沒有執行過 (HAS NEVER BEEN EXECUTED). # 我們把剛剛創建的 wrapper 函數返回. # wrapper 函數包含了這個函數,還有一些需要提前后之后執行的代碼, # 可以直接使用了(It's ready to use!) **return** the_wrapper_around_the_original_function @my_shiny_new_decorator **def** another_stand_alone_function(): print("Leave me alone") another_stand_alone_function() # outputs: # Before the function runs # Leave me alone # After the function runs
是的,這就完了,就這么簡單。@decorator 只是下面這條語句的簡寫(shortcut):
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
裝飾器語法糖其實就是裝飾器模式的一個Python化的變體。為了方便開發,Python已經內置了好幾種經典的設計模式,比如迭代器(iterators)。當然,你還可以堆積使用裝飾器:
def bread(func): **def** wrapper(): print("") func() print("") **return** wrapper **def** ingredients(func): **def** wrapper(): print("#tomatoes#") func() print("~salad~") **return** wrapper **def** sandwich(food="--ham--"): print(food) sandwich() # outputs: --ham-- sandwich = bread(ingredients(sandwich)) sandwich() # outputs: # # #tomatoes# # --ham-- # ~salad~ #
用Python的裝飾器語法表示:
def bread(func): **def** wrapper(): print("") func() print("") **return** wrapper **def** ingredients(func): **def** wrapper(): print("#tomatoes#") func() print("~salad~") **return** wrapper @bread @ingredients **def** sandwich(food="--ham--"): print(food) sandwich() # outputs: # # #tomatoes# # --ham-- # ~salad~ #
裝飾器放置的順序也很重要:
def bread(func): **def** wrapper(): print("") func() print("") **return** wrapper **def** ingredients(func): **def** wrapper(): print("#tomatoes#") func() print("~salad~") **return** wrapper @ingredients @bread **def** strange_sandwich(food="--ham--"): print(food) strange_sandwich() # outputs: ##tomatoes# # # --ham-- ## ~salad~
# 這不是什么黑色魔法(black magic),你只是必須讓wrapper傳遞參數: **def** a_decorator_passing_arguments(function_to_decorate): **def** a_wrapper_accepting_arguments(arg1, arg2): print("I got args! Look:", arg1, arg2) function_to_decorate(arg1, arg2) **return** a_wrapper_accepting_arguments # 當你調用裝飾器返回的函數式,你就在調用wrapper,而給wrapper的 # 參數傳遞將會讓它把參數傳遞給要裝飾的函數 @a_decorator_passing_arguments **def** print_full_name(first_name, last_name): print("My name is", first_name, last_name) print_full_name("Peter", "Venkman") # outputs: # I got args! Look: Peter Venkman # My name is Peter Venkman
在上面的裝飾器調用中,比如@decorator,該裝飾器默認它后面的函數是唯一的參數。裝飾器的語法允許我們調用decorator時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
# a new wrapper layer **def** pre_str(pre=''): # old decorator **def** decorator(F): **def** new_F(a, b): print(pre + " input", a, b) **return** F(a, b) **return** new_F **return** decorator # get square sum @pre_str('^_^') **def** square_sum(a, b): **return** a ** 2 + b ** 2 # get square diff @pre_str('T_T') **def** square_diff(a, b): **return** a ** 2 - b ** 2 print(square_sum(3, 4)) print(square_diff(3, 4)) # outputs: # ('^_^ input', 3, 4) # 25 # ('T_T input', 3, 4) # -7
上面的pre_str是允許參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,并返回一個裝飾器。我們可以將它理解為一個含有環境參量的閉包。當我們使用@pre_str(‘^_^’)調用的時候,Python能夠發現這一層的封裝,并把參數傳遞到裝飾器的環境中。該調用相當于:
square_sum = pre_str('^_^') (square_sum)
Python的一個偉大之處在于:方法和函數幾乎是一樣的(methods and functions are really the same),除了方法的第一個參數應該是當前對象的引用(也就是 self)。這也就意味著只要記住把 self 考慮在內,你就可以用同樣的方法給方法創建裝飾器:
def method_friendly_decorator(method_to_decorate): **def** wrapper(self, lie): lie = lie - 3# very friendly, decrease age even more :-) **return** method_to_decorate(self, lie) **return** wrapper **class** Lucy(object): **def** __init__(self): self.age = 32 @method_friendly_decorator **def** say_your_age(self, lie): print("I am %s, what did you think?" % (self.age + lie)) l = Lucy() l.say_your_age(-3) # outputs: I am 26, what did you think?
當然,如果你想編寫一個非常通用的裝飾器,可以用來裝飾任意函數和方法,你就可以無視具體參數了,直接使用 *args, **kwargs 就行:
def a_decorator_passing_arbitrary_arguments(function_to_decorate): # The wrapper accepts any arguments **def** a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print("Do I have args?:") print(args) print(kwargs) # Then you unpack the arguments, here *args, **kwargs # If you are not familiar with unpacking, check: # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) **return** a_wrapper_accepting_arbitrary_arguments @a_decorator_passing_arbitrary_arguments **def** function_with_no_argument(): print("Python is cool, no argument here.") function_with_no_argument() # outputs # Do I have args?: # () # {} # Python is cool, no argument here. @a_decorator_passing_arbitrary_arguments **def** function_with_arguments(a, b, c): print(a, b, c) function_with_arguments(1, 2, 3) # outputs # Do I have args?: # (1, 2, 3) # {} # 1 2 3 @a_decorator_passing_arbitrary_arguments **def** function_with_named_arguments(a, b, c, platypus="Why not ?"): print("Do %s, %s and %s like platypus? %s" % (a, b, c, platypus)) function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!") # outputs # Do I have args ? : # ('Bill', 'Linus', 'Steve') # {'platypus': 'Indeed!'} # Do Bill, Linus and Steve like platypus? Indeed! **class** Mary(object): **def** __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments **def** say_your_age(self, lie=-3):# You can now add a default value print("I am %s, what did you think ?" % (self.age + lie)) m = Mary() m.say_your_age() # outputs # Do I have args?: # (,) # {} # I am 28, what did you think?
在上面的例子中,裝飾器接收一個函數,并返回一個函數,從而起到加工函數的效果。在Python 2.6以后,裝飾器被拓展到類。一個裝飾器可以接收一個類,并返回一個類,從而起到加工類的效果。
def decorator(aClass): **class** newClass: **def** __init__(self, age): self.total_display = 0 self.wrapped = aClass(age) **def** display(self): self.total_display += 1 print("total display", self.total_display) self.wrapped.display() **return** newClass @decorator **class** Bird: **def** __init__(self, age): self.age = age **def** display(self): print("My age is", self.age) eagleLord = Bird(5) **for** i **in** range(3): eagleLord.display()
在decorator中,我們返回了一個新類newClass。在新類中,我們記錄了原來類生成的對象(self.wrapped),并附加了新的屬性total_display,用于記錄調用display的次數。我們也同時更改了display方法。通過修改,我們的Bird類可以顯示調用display的次數了。
Python中有三種我們經常會用到的裝飾器, property、 staticmethod、 classmethod,他們有個共同點,都是作用于類方法之上。
property 裝飾器用于類中的函數,使得我們可以像訪問屬性一樣來獲取一個函數的返回值。
class XiaoMing: first_name = '明' last_name = '小' @property **def** full_name(self): **return** self.last_name + self.first_name xiaoming = XiaoMing() print(xiaoming.full_name)
例子中我們像獲取屬性一樣獲取 full_name 方法的返回值,這就是用 property 裝飾器的意義,既能像屬性一樣獲取值,又可以在獲取值的時候做一些操作。
staticmethod 裝飾器同樣是用于類中的方法,這表示這個方法將會是一個靜態方法,意味著該方法可以直接被調用無需實例化,但同樣意味著它沒有 self 參數,也無法訪問實例化后的對象。
class XiaoMing: @staticmethod **def** say_hello(): print('同學你好') XiaoMing.say_hello() # 實例化調用也是同樣的效果 # 有點多此一舉 xiaoming = XiaoMing() xiaoming.say_hello()
classmethod 依舊是用于類中的方法,這表示這個方法將會是一個類方法,意味著該方法可以直接被調用無需實例化,但同樣意味著它沒有 self 參數,也無法訪問實例化后的對象。相對于 staticmethod 的區別在于它會接收一個指向類本身的 cls 參數。
class XiaoMing: name = '小明' @classmethod **def** say_hello(cls): print('同學你好, 我是' + cls.name) print(cls) XiaoMing.say_hello()
一個函數不止有他的執行語句,還有著 name(函數名),doc (說明文檔)等屬性,我們之前的例子會導致這些屬性改變。
def decorator(func): **def** wrapper(*args, **kwargs): """doc of wrapper""" print('123') **return** func(*args, **kwargs) **return** wrapper @decorator **def** say_hello(): """doc of say hello""" print('同學你好') print(say_hello.__name__) print(say_hello.__doc__)
由于裝飾器返回了 wrapper 函數替換掉了之前的 say_hello 函數,導致函數名,幫助文檔變成了 wrapper 函數的了。解決這一問題的辦法是通過 functools 模塊下的 wraps 裝飾器。
from functools import wraps **def** decorator(func): @wraps(func) **def** wrapper(*args, **kwargs): """doc of wrapper""" print('123') **return** func(*args, **kwargs) **return** wrapper @decorator **def** say_hello(): """doc of say hello""" print('同學你好') print(say_hello.__name__) print(say_hello.__doc__)
裝飾器的核心作用是name binding。這種語法是Python多編程范式的又一個體現。大部分Python用戶都不怎么需要定義裝飾器,但有可能會使用裝飾器。鑒于裝飾器在Python項目中的廣泛使用,了解這一語法是非常有益的。
設計模式是一個在計算機世界里鼎鼎大名的詞。假如你是一名 Java 程序員,而你一點設計模式都不懂,那么我打賭你找工作的面試過程一定會度過的相當艱難。
但寫 Python 時,我們極少談起“設計模式”。雖然 Python 也是一門支持面向對象的編程語言,但它的鴨子類型設計以及出色的動態特性決定了,大部分設計模式對我們來說并不是必需品。所以,很多 Python 程序員在工作很長一段時間后,可能并沒有真正應用過幾種設計模式。
不過裝飾器模式(Decorator Pattern)是個例外。因為 Python 的“裝飾器”和“裝飾器模式”有著一模一樣的名字,我不止一次聽到有人把它們倆當成一回事,認為使用“裝飾器”就是在實踐“裝飾器模式”。但事實上,它們是兩個完全不同的東西。
“裝飾器模式”是一個完全基于“面向對象”衍生出的編程手法。它擁有幾個關鍵組成:一個統一的接口定義、若干個遵循該接口的類、類與類之間一層一層的包裝。最終由它們共同形成一種“裝飾”的效果。
而 Python 里的“裝飾器”和“面向對象”沒有任何直接聯系,**它完全可以只是發生在函數和函數間的把戲。事實上,“裝飾器”并沒有提供某種無法替代的功能,它僅僅就是一顆“語法糖”而已。下面這段使用了裝飾器的代碼:
@log_time @cache_result **def** foo(): pass
基本完全等同于:
def foo(): pass foo = log_time(cache_result(foo))
裝飾器最大的功勞,在于讓我們在某些特定場景時,可以寫出更符合直覺、易于閱讀的代碼。它只是一顆“糖”,并不是某個面向對象領域的復雜編程模式。
關于“Python裝飾器怎么應用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。