注意: 這是一篇 StackOverflow 上的問題回答,因為這個回答很棒,所以我把它存檔了
問: 怎樣在 Python 中連續使用多個函式裝飾器?
如果你不想看詳細的解釋,你可以看 Paolo Bergantino 的回答
裝飾器基礎
Python 的裝飾器都是物件
為了理解裝飾器,你首先必須知道 Python 中的函式都是 object 物件。 這非常重要。讓我們通過一個例子來看看原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
def shout(word='yes'): return word.capitalize() + '!' print shout() # outputs : 'Yes!' # 作為一個 object 物件,你可以把一個函式分配給一個變數,就像是 # 其他 object 物件一樣 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 函式另一個有趣的性質在於它們可以。。。在另一個函式內部定義!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
def talk(): # 你可以在 `talk` 函式臨時定義一個函式 def whisper(word='yes'): return word.lower() + '...' # ... 之後直接使用這個函式 print whisper() # 你可以呼叫 `talk` 函式,每次呼叫這個函式都會定義 `whisper` 函式,並且 # 在 `talk` 函式中呼叫 `whisper` 函式 talk() # outputs: # "yes..." # 但是 `whisper` 函式在 `talk` 函式外部並不存在: try: print whisper() except NameError as e: print e #outputs : "name 'whisper' is not defined"* #Python's functions are objects |
函式引用
現在是比較有趣的部分。。。
你已經知道了函式是 object 物件。此外,函式還:
- 可以像變數一樣賦值
- 可以在另一個函式內部定義
這表示 函式可以 return
另一個函式。看下面吧!☺
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
def getTalk(kind='shout'): # 我們臨時定義一個函式 def shout(word='yes'): return word.capitalize() + '!' def whisper(word='yes'): return word.lower() + '...' # 然後我們返回上面兩個函式中的一個 if kind == 'shout': # 我們並沒有使用 '()' 。因此我們並沒有呼叫函式; # 相反,我們返回了函式物件 return shout else: return whisper # 你該怎樣使用這個奇怪的功能呢? # 呼叫這個函式,然後把結果賦值給一個變數 talk = getTalk() # 你可以看到 `talk` 是一個函式物件: print talk #outputs : <function shout at 0xb7ea817c> # The object is the one returned by the function: # 這個物件是由一個函式返回的 print talk() #outputs : Yes! # 如果你覺得奇怪的話,你甚至可以直接使用它 print getTalk('whisper')() #outputs : yes... |
但等等…還有一些內容!
如果你可以 return
一個函式,那麼你也可以把函式當作引數傳遞:
1 2 3 4 5 6 7 8 |
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! |
好,你已經掌握了裝飾器所需的全部知識。正如你所見,裝飾器是“包裝器”,也就是說 它們允許你在它們裝飾的函式的前面和後面執行其他程式碼 ,而不必修改函式本身。
動手製作裝飾器
你應該怎樣動手製作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# 裝飾器是把其他函式作為引數的函式 def my_shiny_new_decorator(a_function_to_decorate): # 在裝飾器內部,裝飾器臨時建立了一個函式: 包裝器。 # 這個函式把原來的函式包裝起來 # 因此它可以在原函式的前面和後面執行其他程式碼。 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` 還從未執行過。 # 我們返回剛剛建立的包裝器 # 包裝器中包含了原函式和在原函式之前/之後執行的程式碼。現在已經可以使用了! return the_wrapper_around_the_original_function # 現在想象一下你建立了一個函式,你不想再改動它了。 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 |
現在,你希望每次你呼叫 a_stand_alone_function
的時候,實際上 a_stand_alone_function_decorated
會被呼叫。也就是說,這只是用 my_shiny_new_decorator
返回的函式重寫了 a_stand_alone_function
函式:
1 2 3 4 5 6 7 8 |
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 # 你猜怎樣著?這實際上就是裝飾器的原理! |
裝飾器解密
和前面相同的例子,但是使用了裝飾器語法:
1 2 3 4 5 6 7 8 9 |
@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
只是下面形式的簡寫:
1 |
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function) |
裝飾器只是一個 pythonic 的裝飾器設計模式的變種。Python 中內建了許多種傳統的設計模式來簡化開發過程(例如迭代器)。
當然,你可以疊加多個裝飾器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 的裝飾器語法:
1 2 3 4 5 6 7 8 9 10 11 12 |
<a href="http://www.jobbole.com/members/bread">@bread</a> @ingredients def sandwich(food='--ham--'): print food sandwich() #outputs: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/> |
你設定裝飾器的順序很重要:
1 2 3 4 5 6 7 8 9 10 11 12 |
@ingredients <a href="http://www.jobbole.com/members/bread">@bread</a> def strange_sandwich(food='--ham--'): print food strange_sandwich() #outputs: ##tomatoes# #</''''''\> # --ham-- #<\______/> # ~salad~ |
現在:是時候回答問題了。。。
現在你很容易就知道怎樣回答這個問題了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# 生成粗體(bold)的裝飾器 def makebold(fn): # 裝飾器返回的新函式 def wrapper(): # 在之前和之後插入其他程式碼 return '<b>' + fn() + '</b>' return wrapper # 生成斜體的裝飾器 def makeitalic(fn): # 裝飾器返回的新函式 def wrapper(): # 在函式執行前後插入一些程式碼 return '<i>' + fn() + '</i>' return wrapper @makebold @makeitalic def say(): return 'hello' print say() #outputs: <b><i>hello</i></b> # 和上面完全等價的形式 def say(): return 'hello' say = makebold(makeitalic(say)) print say() #outputs: <b><i>hello</i></b> |
現在你該放下輕鬆的心態,好好看看裝飾器的高階使用方法了。
把裝飾器傳到下一層去
把引數傳遞給被裝飾的函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 這並不是黑魔法,你只是讓包裝器傳遞引數而已 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 # 因為當你呼叫裝飾器返回的函式時,實際上你在呼叫包裝器,把引數傳遞給包裝器,這也就完成了把引數傳遞給裝飾器函式 @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 |
裝飾器方法
關於 Python 的一個優點就是方法和函式本質本質上是一樣的。二者唯一的區別就是方法的第一個引數是對當前物件的引用 (self
)。
這意味著你可以按照同樣的方式為方法建立裝飾器!只要記得考慮 self
就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 sayYourAge(self, lie): print 'I am {0}, what did you think?'.format(self.age + lie) l = Lucy() l.sayYourAge(-3) #outputs: I am 26, what did you think? |
如果你在建立通用的裝飾器 — 一個適用於任何函式或者方法的裝飾器,無論引數是什麼 — 那麼只要使用 *args, **kwargs
就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
def a_decorator_passing_arbitrary_arguments(function_to_decorate): # 包裝器接受任何引數 def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print 'Do I have args?:' print args print kwargs # 接下來解包引數,也就是這裡的 *args, **kwargs # 如果你不熟悉解包,可以瀏覽這個: # 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 {0}, {1} and {2} like platypus? {3}'.format( 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 sayYourAge(self, lie=-3): #你可以在這裡新增預設值 print 'I am {0}, what did you think?'.format(self.age + lie) m = Mary() m.sayYourAge() #outputs # Do I have args?: #(<__main__.Mary object at 0xb7d303ac>,) #{} #I am 28, what did you think? |
把引數傳遞給裝飾器
太棒了,現在你對於把引數傳遞給裝飾器本身有什麼看法呢?
這可能有點奇怪,因為裝飾器必須接收一個函式作為引數。因此,你可能無法直接把裝飾器函式作為引數傳遞給另一個裝飾器。
在得到答案之前,讓我們寫一個小的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 裝飾器是普通函式 def my_decorator(func): print 'I am an ordinary function' def wrapper(): print 'I am function returned by the decorator' func() return wrapper # 因此你可以在沒有任何 '@' 的情況下呼叫它 def lazy_function(): print 'zzzzzzzz' decorated_function = my_decorator(lazy_function) #outputs: I am an ordinary function # 上面的函式輸出 'I am an ordinary function' ,因為這實際上就是我們直接呼叫函式的結果。沒什麼好奇怪的。 @my_decorator def lazy_function(): print 'zzzzzzzz' #outputs: I am an ordinary function |
結果是一模一樣的:my_decorator
被呼叫了。因此當你使用 @my_decorator
時,Python 會呼叫 “my_decorator
” 變數所代表的函式。
這很重要!你提供的這個變數可以指向裝飾器,也可以不指向。
讓我們增加點難度。 ☺
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
def decorator_maker(): print 'I make decorators! I am executed only once: '+\ 'when you make me create a decorator.' def my_decorator(func): print 'I am a decorator! I am executed only when you decorate a function.' def wrapped(): print ('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.') return func() print 'As the decorator, I return the wrapped function.' return wrapped print 'As a decorator maker, I return a decorator' return my_decorator # 讓我們建立一個裝飾器。本質上是一個新函式 new_decorator = decorator_maker() #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator # 然後我們裝飾下面這個函式 def decorated_function(): print 'I am the decorated function.' decorated_function = new_decorator(decorated_function) #outputs: #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function # 呼叫這個函式 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. |
沒什麼意料之外的事情發生。
我們再做一次上面的事情,只不過這一次取消掉所有的中間變數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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. # 最後: 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. |
讓它更短一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@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. #最後: 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. |
你注意到了嗎?我們呼叫了一個 @
語法的函式! :-)
所以,回到裝飾器的引數上面來。如果我們可以使用函式生成一個臨時的裝飾器,我們也可以把引數傳遞給那個函式,對嗎?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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): # 傳遞引數的能力來自於閉包 # 如果你不瞭解閉包,那也沒關係, # 或者你也可以閱讀 http://stackoverflow.com/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 # 不要混淆裝飾器引數和函式引數! 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 |
最後得到的就是:帶引數的裝飾器。引數可以設定為變數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
c1 = 'Penny' c2 = 'Leslie' @decorator_maker_with_arguments('Leonard', c1) 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(c2, 'Howard') #outputs: #I make decorators! And I accept arguments: Leonard Penny #I am the decorator. Somehow you passed me arguments: Leonard Penny #I am the wrapper around the decorated function. #I can access all the variables # - from the decorator: Leonard Penny # - from the function call: Leslie Howard #Then I can pass them to the decorated function #I am the decorated function and only knows about my arguments: Leslie Howard |
如你所見,你可以使用這個技巧向裝飾器傳遞引數,就像是向普通函式傳遞一樣。如果你願意的話,你甚至可以使用 *args, **kwargs
。但記住,裝飾器只會被呼叫一次。只在 Python 匯入指令碼的時候執行。在這之後你就無法動態設定引數了。當你執行 import x
之後,函式已經被裝飾了,因此之後你無法改變任何東西。
練習: 裝飾一個裝飾器
好的,作為獎勵,我會提供你一段程式碼允許裝飾器接收任何引數。畢竟,為了接收引數,我們會用另一個函式建立裝飾器。
我們包裝一下裝飾器。
我們最近看到的有包裝函式的還有什麼呢?
對了,就是裝飾器!
讓我們做點有趣的事,寫一個裝飾器的裝飾器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def decorator_with_args(decorator_to_enhance): """ 這個函式是被用作裝飾器。 它會裝飾其他函式,被裝飾的函式也是一個裝飾器。 喝杯咖啡吧。 它允許任何裝飾器接收任意個引數, 這樣你就不會為每次都要考慮怎樣處理而頭疼了 """ # 我們使用同樣的技巧來傳遞引數 def decorator_maker(*args, **kwargs): # 我們建立一個僅可以接收一個函式的臨時裝飾器 # 但無法從 maker 傳遞引數 def decorator_wrapper(func): # 原裝飾器返回的結果 # 其實只是一個普通函式(這個函式返回一個函式)。 # 唯一的陷阱是: 裝飾器必須有特定的格式,否則無法執行: return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_maker |
可以像下面這樣使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 建立一個用作裝飾器的函式。然後加上一個裝飾器 :-) # 不要忘記,格式是 `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 # 然後用全新的裝飾器裝飾你的函式。 @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! |
我知道,上次你有這種感覺,是在聽一個人說:“在理解遞迴之前,你必須首先理解遞迴” 時。但現在,掌握了這個之後你不覺得很棒嗎?
最佳實踐: 裝飾器
- 裝飾器在 Python 2.4 引進,因此確保你的程式碼執行的 Python 版本 >=2.4
- 裝飾器會拖慢函式呼叫速度。請牢記
- 你無法解除裝飾一個函式。 (確實 有 一些技巧可以建立允許解除裝飾的裝飾器,但是沒人會使用它們。)因此一旦函式被裝飾了,所有這個函式的程式碼就都裝飾了。
- 裝飾器包裝函式,會使得函式更難除錯。 (從 Python >=2.5 有所好轉;看下文。)
functools
模組在 Python 2.5 引進。模組中包含了函式 functools.wraps()
,這個函式會把被裝飾函式的名字,模組名,docstring 都複製到它的包裝器中。
(有趣的事情是: functools.wraps()
是個裝飾器!☺)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# 至於除錯,stacktrace 輸出函式的 __name__ def foo(): print 'foo' print foo.__name__ #outputs: foo # 有了裝飾器之後,有點混亂 def bar(func): def wrapper(): print 'bar' return func() return wrapper @bar def foo(): print 'foo' print foo.__name__ #outputs: wrapper # `functools` 可以改善上面的情況 import functools def bar(func): # 我們認為 `wrapper` 正在包裝 `func` # 神奇的事情發生了 @functools.wraps(func) def wrapper(): print 'bar' return func() return wrapper @bar def foo(): print 'foo' print foo.__name__ #outputs: foo |
怎樣使裝飾器變得有用?
現在最大的問題是: 我可以用裝飾器來幹嘛?
裝飾器看起來很酷,很強大,但有一個實用的例子就更好了。大概有 1000 種可能的例子。常見的使用方法是擴充套件一個外部庫函式(你無法修改)的行為,或者用來除錯外部庫函式(你不想修改它,因為它是臨時函式)。
你可以使用裝飾器以 DRY(Don’t Repeat Yourself,不重複自己) 的方式擴充套件函式,就像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
def benchmark(func): """ 一個用來輸出函式執行時間的裝飾器 """ 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): """ 一個用來記錄指令碼活動的裝飾器。 (實際上只是列印出來,但可以輸出到日誌!) """ def wrapper(*args, **kwargs): res = func(*args, **kwargs) print func.__name__, args, kwargs return res return wrapper def counter(func): """ 一個用來統計並輸出函式執行次數的裝飾器 """ 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 |
當然,裝飾器的優點就在於你可以在不重寫函式的前提下,使用在幾乎任何函式上。DRY(Don’t Repeat Yourself,不要重複你自己),正如我說的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@counter @benchmark @logging def get_random_futurama_quote(): from urllib import urlopen result = urlopen('http://subfusion.net/cgi-bin/quote.pl?quote=futurama').read() try: value = result.split('<br><b><hr><br>')[1].split('<br><br><hr>')[0] return value.strip() except: return 'No, I’m ... doesn’t!' print get_random_futurama_quote() print get_random_futurama_quote() #outputs: #get_random_futurama_quote () {} #wrapper 0.02 #wrapper has been used: 1x #The laws of science be a harsh mistress. #get_random_futurama_quote () {} #wrapper 0.01 #wrapper has been used: 2x #Curse you, merciful Poseidon! |
Python 本身提供了幾種裝飾器: property
,staticmethod
,等
- Django 使用裝飾器來管理快取,檢視許可權。
- Twisted 用它來偽造內聯非同步函式呼叫。
裝飾器的用途確實很廣。