小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Python裝飾器的學(xué)習(xí)筆記

 LibraryPKU 2017-09-16

鏈接:http://www./python-decorator.html(點擊尾部閱讀原文前往)

原文:http:///questions/739654/how-to-make-a-chain-of-function-decorators-in-python#answer-1594484


裝飾器(decorator)是一種高級Python語法。可以對一個函數(shù)、方法或者類進(jìn)行加工。在Python中,我們有多種方法對函數(shù)和類進(jìn)行加工,相對于其它方式,裝飾器語法簡單,代碼可讀性高。因此,裝飾器在Python項目中有廣泛的應(yīng)用。修飾器經(jīng)常被用于有切面需求的場景,較為經(jīng)典的有插入日志、性能測試、事務(wù)處理, Web權(quán)限校驗, Cache等。很有名的例子,就是咖啡,加糖的咖啡,加牛奶的咖啡。本質(zhì)上,還是咖啡,只是在原有的東西上,做了“裝飾”,使之附加一些功能或特性。


裝飾器的優(yōu)點是能夠抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。即,可以將函數(shù)“修飾”為完全不同的行為,可以有效的將業(yè)務(wù)邏輯正交分解。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。例如記錄日志,需要對某些函數(shù)進(jìn)行記錄。笨的辦法,每個函數(shù)加入代碼,如果代碼變了,就悲催了。裝飾器的辦法,定義一個專門日志記錄的裝飾器,對需要的函數(shù)進(jìn)行裝飾。

Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名前面加一個@XXX注解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很讓人望而卻步,在使用它之前你需要了解一堆Annotation的類庫文檔,讓人感覺就是在學(xué)另外一門語言。而Python使用了一種相對于Decorator Pattern和Annotation來說非常優(yōu)雅的方法,這種方法不需要你去掌握什么復(fù)雜的OO模型或是Annotation的各種類庫規(guī)定,完全就是語言層面的玩法:一種函數(shù)式編程的技巧。


在Python中,裝飾器實現(xiàn)是十分方便。原因是:函數(shù)可以被扔來扔去。


python的函數(shù)就是對象


要理解裝飾器,就必須先知道,在python里,函數(shù)也是對象(functions are objects)。明白這一點非常重要,讓我們通過一個例子來看看為什么。

def shout(word='yes'):          return word.capitalize()+'!'      
print shout()      # outputs : 'Yes!'      
# 作為一個對象,你可以像其他對象一樣把函數(shù)賦值給其他變量      
scream = shout      # 注意我們沒有用括號:我們不是在調(diào)用函數(shù),      
# 而是把函數(shù)'shout'的值綁定到'scream'這個變量上      
# 這也意味著你可以通過'scream'這個變量來調(diào)用'shout'函數(shù)      
print scream()      # outputs : 'Yes!'      
# 不僅如此,這也還意味著你可以把原來的名字'shout'刪掉,      
# 而這個函數(shù)仍然可以通過'scream'來訪問      
del shout      try:          print shout()      except NameError, e:          print e          #outputs: 'name 'shout' is not defined'    
 
print scream()      outputs: 'Yes!'

python 函數(shù)的另一個有趣的特性是,它們可以在另一個函數(shù)體內(nèi)定義。


def talk():      # 你可以在 'talk' 里動態(tài)的(on the fly)定義一個函數(shù)...      def whisper(word='yes'):          return word.lower()+'...'      # ... 然后馬上調(diào)用它!      print whisper()   # 每當(dāng)調(diào)用'talk',都會定義一次'whisper',然后'whisper'在'talk'里被調(diào)用  
talk()   # outputs:  
# 'yes...'  
# 但是'whisper' 在 'talk'外并不存在:  
try:      print whisper()   except NameError, e:      print e   #outputs : 'name 'whisper' is not defined'*

函數(shù)引用(Functions references)


你剛剛已經(jīng)知道了,python的函數(shù)也是對象,因此:


  • 可以被賦值給變量

  • 可以在另一個函數(shù)體內(nèi)定義


那么,這樣就意味著一個函數(shù)可以返回另一個函數(shù) :-),來看個例子:

def getTalk(type='shout'):      # 我們先動態(tài)定義一些函數(shù)      def shout(word='yes'):          return word.capitalize()+'!'      def whisper(word='yes') :          return word.lower()+'...';      # 然后返回其中一個      if type == 'shout':          # 注意:我們是在返回函數(shù)對象,而不是調(diào)用函數(shù),          # 所以不要用到括號 '()'          return shout        else:          return whisper   # 那你改如何使用這個怪獸呢?(How do you use this strange beast?)   # 先把函數(shù)賦值給一個變量  
talk = getTalk()        # 你可以發(fā)現(xiàn) 'talk' 其實是一個函數(shù)對象:  
print talk   #outputs :  
# 這個對象就是 getTalk 函數(shù)返回的:  
print talk()   #outputs : Yes!  
# 你甚至還可以直接這樣使用(if you feel wild):  
print getTalk('whisper')()   #outputs : yes...

既然可以返回一個函數(shù),那么也就可以像參數(shù)一樣傳遞:

def doSomethingBefore(func):      print 'I do something before then I call the function you gave me'      print func()   doSomethingBefore(scream)   #outputs:  
#I do something before then I call the function you gave me   #Yes!

現(xiàn)在已經(jīng)具備了理解裝飾器的所有基礎(chǔ)知識了。裝飾器也就是一種包裝材料,它們可以讓你在執(zhí)行被裝飾的函數(shù)之前或之后執(zhí)行其他代碼,而且不需要修改函數(shù)本身。


手工制作裝飾器(Handcrafted decorators)


你可以像這樣來定制:

# 一個裝飾器是一個需要另一個函數(shù)作為參數(shù)的函數(shù)      
def my_shiny_new_decorator(a_function_to_decorate):          # 在裝飾器內(nèi)部動態(tài)定義一個函數(shù):wrapper(原意:包裝紙).          # 這個函數(shù)將被包裝在原始函數(shù)的四周          # 因此就可以在原始函數(shù)之前和之后執(zhí)行一些代碼.          def the_wrapper_around_the_original_function():              # 把想要在調(diào)用原始函數(shù)前運行的代碼放這里              print 'Before the function runs'              # 調(diào)用原始函數(shù)(需要帶括號)              a_function_to_decorate()              # 把想要在調(diào)用原始函數(shù)后運行的代碼放這里              print 'After the function runs'          # 直到現(xiàn)在,'a_function_to_decorate'還沒有執(zhí)行過 (HAS NEVER BEEN EXECUTED).          # 我們把剛剛創(chuàng)建的 wrapper 函數(shù)返回.          # wrapper 函數(shù)包含了這個函數(shù),還有一些需要提前后之后執(zhí)行的代碼,          # 可以直接使用了(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      # 現(xiàn)在,你可以裝飾一下來修改它的行為.      
# 只要簡單的把它傳遞給裝飾器,后者能用任何你想要的代碼動態(tài)的包裝      # 而且返回一個可以直接使用的新函數(shù):      
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

現(xiàn)在你大概希望,每次調(diào)用 a_stand_alone_function 時,實際調(diào)用的是a_stand_alone_function_decorated 。這很容易,只要把 my_shiny_new_decorator 返回的函數(shù)覆蓋 a_stand_alone_function 就可以了:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)   a_stand_alone_function()   #outputs:  
#Before the function runs  
#I am a stand alone function, don't you dare modify me  
#After the function runs  
# And guess what? That's EXACTLY what decorators do!

揭秘裝飾器(Decorators demystified)


我們用裝飾器的語法來重寫一下前面的例子:

@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化的變體(pythonic variant)。為了方便開發(fā),python已經(jīng)內(nèi)置了好幾種經(jīng)典的設(shè)計模式,比如迭代器(iterators)。 當(dāng)然,你還可以堆積使用裝飾器(you can cumulate decorators):

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的裝飾器語法表示:




裝飾器放置的順序 很重要:




給裝飾器函數(shù)傳參(Passing arguments to the decorated function)




含參的裝飾器


在上面的裝飾器調(diào)用中,比如@decorator,該裝飾器默認(rèn)它后面的函數(shù)是唯一的參數(shù)。裝飾器的語法允許我們調(diào)用decorator時,提供其它參數(shù),比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。




上面的pre_str是允許參數(shù)的裝飾器。它實際上是對原有裝飾器的一個函數(shù)封裝,并返回一個裝飾器。我們可以將它理解為一個含有環(huán)境參量的閉包。當(dāng)我們使用@pre_str(‘^_^’)調(diào)用的時候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。該調(diào)用相當(dāng)于:



裝飾方法(Decorating methods)


Python的一個偉大之處在于:方法和函數(shù)幾乎是一樣的(methods and functions are really the same),除了方法的第一個參數(shù)應(yīng)該是當(dāng)前對象的引用(也就是 self)。這也就意味著只要記住把 self 考慮在內(nèi),你就可以用同樣的方法給方法創(chuàng)建裝飾器了:



當(dāng)然,如果你想編寫一個非常通用的裝飾器,可以用來裝飾任意函數(shù)和方法,你就可以無視具體參數(shù)了,直接使用 *args, **kwargs 就行:




裝飾類


在上面的例子中,裝飾器接收一個函數(shù),并返回一個函數(shù),從而起到加工函數(shù)的效果。在Python 2.6以后,裝飾器被拓展到類。一個裝飾器可以接收一個類,并返回一個類,從而起到加工類的效果。




在decorator中,我們返回了一個新類newClass。在新類中,我們記錄了原來類生成的對象(self.wrapped),并附加了新的屬性total_display,用于記錄調(diào)用display的次數(shù)。我們也同時更改了display方法。通過修改,我們的Bird類可以顯示調(diào)用display的次數(shù)了。


給裝飾器傳參(Passing arguments to the decorator)


現(xiàn)在對于給裝飾器本身傳參數(shù),你有什么看法呢?好吧,這樣說有點繞,因為裝飾器必須接受一個函數(shù)作為參數(shù),所以就不能把被裝飾的函數(shù)的參數(shù),直接傳給裝飾器(you cannot pass the decorated function arguments directly to the decorator.)


在直奔答案之前,我們先寫一個小提示:




這完全一樣,都是 my_decorator 被調(diào)用。所以當(dāng)你使用 @my_decorator 時,你在告訴 python 去調(diào)用 “被變量 my_decorator 標(biāo)記的” 函數(shù)(the function ‘labeled by the variable “my_decorator”‘)。這很重要,因為你給的這個標(biāo)簽?zāi)苤苯又赶蜓b飾器或者其他!





不要感到驚訝,讓我們做一件完全一樣的事情,只不過跳過了中間變量:

def decorated_function():      print 'I am the decorated function.'  
decorated_function = decorator_maker()(decorated_function)   #outputs:  
#I make decorators! I am executed only once: when you make me create a decorator.  
#As a decorator maker, I return a decorator  
#I am a decorator! I am executed only when you decorate a function.  
#As the decorator, I return the wrapped function.  
# Finally:  
decorated_function()      #outputs:  
#I am the wrapper around the decorated function. I am called when you call the decorated function.  
#As the wrapper, I return the RESULT of the decorated function.   #I am the decorated function.

再做一次,代碼甚至更短:

@decorator_maker()  
def decorated_function():      print 'I am the decorated function.'  
#outputs:  
#I make decorators! I am executed only once: when you make me create a decorator.  
#As a decorator maker, I return a decorator  
#I am a decorator! I am executed only when you decorate a function.  
#As the decorator, I return the wrapped function.  
#Eventually:  
decorated_function()      #outputs:  
#I am the wrapper around the decorated function. I am called when you call the decorated function.  
#As the wrapper, I return the RESULT of the decorated function.   #I am the decorated function.

我們在用 @ 語法調(diào)用了函數(shù) , 那么回到帶參數(shù)的裝飾器。如果我們能夠使用一個函數(shù)動態(tài)(on the fly)的生成裝飾器,那么我們就能把參數(shù)傳遞給那個函數(shù),對嗎?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):      print 'I make decorators! And I accept arguments:', decorator_arg1, decorator_arg2      def my_decorator(func):          # 在這里能傳參數(shù)是一個來自閉包的饋贈.          # 如果你對閉包感到不舒服,你可以直接忽略(you can assume it's ok),          # 或者看看這里: http:///questions/13857/can-you-explain-closures-as-they-relate-to-python          print 'I am the decorator. Somehow you passed me arguments:', decorator_arg1, decorator_arg2          # 不要把裝飾器參數(shù)和函數(shù)參數(shù)搞混了!          def wrapped(function_arg1, function_arg2) :              print ('I am the wrapper around the decorated function.\n'                    'I can access all the variables\n'                    '\t- from the decorator: {0} {1}\n'                    '\t- from the function call: {2} {3}\n'                    'Then I can pass them to the decorated function'                    .format(decorator_arg1, decorator_arg2,                            function_arg1, function_arg2))              return func(function_arg1, function_arg2)          return wrapped      return my_decorator   @decorator_maker_with_arguments('Leonard', 'Sheldon')   def decorated_function_with_arguments(function_arg1, function_arg2):      print ('I am the decorated function and only knows about my arguments: {0}'             ' {1}'.format(function_arg1, function_arg2))   decorated_function_with_arguments('Rajesh', 'Howard')   #outputs:  
#I make decorators! And I accept arguments: Leonard Sheldon  
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon  
#I am the wrapper around the decorated function.  
#I can access all the variables  
#   - from the decorator: Leonard Sheldon  
#   - from the function call: Rajesh Howard  
#Then I can pass them to the decorated function  
#I am the decorated function and only knows about my arguments: Rajesh Howard

這就是了,帶參數(shù)的裝飾器。參數(shù)也可以設(shè)置為變量:




如你所見,你可以給裝飾器傳遞參數(shù),就好像其他任意一個使用了這種把戲的函數(shù)一樣(。如果你愿意,甚至可以使用 *args, **kwargs。但是,記住,裝置器只調(diào)用一次,僅當(dāng)python導(dǎo)入這個腳本時。你不能在之后動態(tài)的設(shè)置參數(shù)。當(dāng)你執(zhí)行 import x 時,這個函數(shù)已經(jīng)被裝飾了,因此你不能修改任何東西。


實踐:裝飾器裝飾一個裝飾器(Let’s practice: a decorator to decorate a decorator)


我將展示一段能用來創(chuàng)建能接受通用的任意參數(shù)的裝飾器的代碼。畢竟,為了能接受參數(shù),我們用了另一個函數(shù)來創(chuàng)建我們的裝飾器。我們包裝了裝飾器。在我們剛剛看到的東西里,還有用來包裝函數(shù)的嗎?是的,就是裝飾器。讓我們給裝飾器寫一個裝飾器來玩玩:


def decorator_with_args(decorator_to_enhance):      '''      This function is supposed to be used as a decorator.      It must decorate an other function, that is intended to be used as a decorator.      Take a cup of coffee.      It will allow any decorator to accept an arbitrary number of arguments,      saving you the headache to remember how to do that every time.      '''      # We use the same trick we did to pass arguments      def decorator_maker(*args, **kwargs):          # We create on the fly a decorator that accepts only a function          # but keeps the passed arguments from the maker.          def decorator_wrapper(func):              # We return the result of the original decorator, which, after all,              # IS JUST AN ORDINARY FUNCTION (which returns a function).              # Only pitfall: the decorator must have this specific signature or it won't work:              return decorator_to_enhance(func, *args, **kwargs)          return decorator_wrapper   return decorator_maker

它可以像這樣使用:

# You create the function you will use as a decorator. And stick a decorator on it :-)  
# Don't forget, the signature is 'decorator(func, *args, **kwargs)'  
@decorator_with_args  
def decorated_decorator(func, *args, **kwargs):      def wrapper(function_arg1, function_arg2):          print 'Decorated with', args, kwargs          return func(function_arg1, function_arg2)      return wrapper   # Then you decorate the functions you wish with your brand new decorated decorator.  
@decorated_decorator(42, 404, 1024)  
def decorated_function(function_arg1, function_arg2):      print 'Hello', function_arg1, function_arg2   decorated_function('Universe and', 'everything')   #outputs:  
#Decorated with (42, 404, 1024) {}  
#Hello Universe and everything  
# Whoooot!

我知道,你上一次有這種感覺,是在聽一個人說“在理解遞歸之前,你必須先理解遞歸”之后。但是現(xiàn)在,掌握之后,你不覺得很爽嗎?


裝飾器最佳實踐(Best practices while using decorators)


裝飾器是在 python 2.4 之后才有的,所以先確定你的代碼運行時;

記住這點:裝飾器降低了函數(shù)調(diào)用效率;


你不能“解裝飾”一個函數(shù)。有一些能用來創(chuàng)建可以移除的裝飾器的方法,但沒人用它們。所以一個函數(shù)一旦被裝飾了,就結(jié)束了(不能改變了)。

裝飾器包裝了函數(shù),這使得會難以調(diào)試。


Python 2.5 通過提供了一個 functools 模塊解決了最后一個問題。functools.wraps 把任意被包裝函數(shù)的函數(shù)名、模塊名和 docstring 拷貝給了 wrapper. 有趣的事是,functools.wraps 也是一個裝飾器:-)




裝飾器如何才能有用(How can the decorators be useful?)


現(xiàn)在問題來了:我能用裝飾器來干嘛?看起來很酷也很強大,但是來一個實際例子才更好。一個典型的用途是,用來擴(kuò)展一個外部導(dǎo)入的函數(shù)(你不能修改)的行為,或者為了調(diào)試(你不想修改這個函數(shù),因為只是暫時的)。你也可以用裝飾器實現(xiàn)只用一段相同的代碼來擴(kuò)展成幾個不同的函數(shù),而且你不需要每次都重寫這段代碼。這樣就是常說的 DRY。比如:

def benchmark(func):      '''      A decorator that prints the time a function takes      to execute.      '''      import time      def wrapper(*args, **kwargs):          t = time.clock()          res = func(*args, **kwargs)          print func.__name__, time.clock()-t          return res      return wrapper   def logging(func):      '''      A decorator that logs the activity of the script.      (it actually just prints it, but it could be logging!)      '''      def wrapper(*args, **kwargs):          res = func(*args, **kwargs)          print func.__name__, args, kwargs          return res      return wrapper   def counter(func):      '''      A decorator that counts and prints the number of times a function has been executed      '''      def wrapper(*args, **kwargs):          wrapper.count = wrapper.count + 1          res = func(*args, **kwargs)          print '{0} has been used: {1}x'.format(func.__name__, wrapper.count)          return res      wrapper.count = 0      return wrapper   @counter  

@benchmark  
logging  

def reverse_string(string):      return str(reversed(string))   print reverse_string('Able was I ere I saw Elba')   print reverse_string('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!')   #outputs:  
#reverse_string ('Able was I ere I saw Elba',) {}  
#wrapper 0.0  
#wrapper has been used: 1x  
#ablE was I ere I saw elbA  
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}  
#wrapper 0.0  
#wrapper has been used: 2x  
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

當(dāng)然,裝飾器的好處就是你可以幾乎用來裝飾所有東西,而且不要重寫。也就是我說的 DRY:



Python 語言本身也提供了一些裝飾器:property、staticmethod 等。Django 用裝飾器來管理換成和視圖權(quán)限。Twisted 用來偽裝 內(nèi)聯(lián)異步函數(shù)調(diào)用。


總結(jié)


裝飾器的核心作用是name binding。這種語法是Python多編程范式的又一個體現(xiàn)。大部分Python用戶都不怎么需要定義裝飾器,但有可能會使用裝飾器。鑒于裝飾器在Python項目中的廣泛使用,了解這一語法是非常有益的。



●本文編號79,以后想閱讀這篇文章直接輸入79即可。

●輸入m可以獲取到文章目錄

推薦15個技術(shù)類公眾微信

涵蓋:程序人生、算法與數(shù)據(jù)結(jié)構(gòu)、黑客技術(shù)與網(wǎng)絡(luò)安全、大數(shù)據(jù)技術(shù)、前端開發(fā)、Java、Python、Web開發(fā)、安卓開發(fā)、iOS開發(fā)、C/C++、.NET、Linux、數(shù)據(jù)庫、運維等。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多