關(guān)鍵詞:Python橱脸、裝飾器础米、裝飾器的深入討論分苇、Python decorator
前言
最近學(xué)習(xí)Python,在看一個框架源碼過程中對裝飾器很困惑,Google了一圈椭盏,在stack overflow的一個問題討論下面找到了這個總結(jié)组砚,這里幾乎有關(guān)于Python全部的內(nèi)容。覺得很好掏颊,遂翻譯過來糟红。翻譯基本都是意譯,能看英文的還是盡量看上面鏈接過去的原版吧乌叶!
Pyton裝飾器基礎(chǔ)
在Python中盆偿,函數(shù)也是對象
為了理解裝飾器,你必須首先理解准浴,在Python中函數(shù)也是對象事扭。
理解這個知識點很重要。讓我們使用一個簡單的例子來說明一下:
def shout(word="yes"):
return word.capitalize()+"!"
print shout()
# 輸出為: 'Yes!'
# 函數(shù)作為一個對象乐横,你可以像其他對象一樣求橄,把它賦值給其他的變量
scream = shout
# 注意我們沒有使用圓括號:我們不是調(diào)用這個函數(shù),我們把"shout"這個函數(shù)賦值給變量"scream"
# 那意味著你之后可以使用"scream"來調(diào)用"shout"這個函數(shù)
print scream()
# 輸出為: 'Yes!'
# 不僅如此葡公,那意味著你可以移除'shout'這個老的名稱罐农,但這個函數(shù)仍然可以通過'scream'訪問
del shout
try:
print shout()
except NameError, e:
print e
# 輸出為: "name 'shout' is not defined"
print scream()
# 輸出為: 'Yes!'
好了,在心里記住這個知識點催什。我們之后很快要用到它涵亏。
Python中函數(shù)還有另一個有趣的特性,那就是它可以在其他函數(shù)里面定義蒲凶!
def talk():
# 你可以在"talk"函數(shù)中定義一個函數(shù)...
def whisper(word="yes"):
return word.lower()+"..."
# ...并且你可以馬上使用這個函數(shù)
print whisper()
# 你每次調(diào)用"talk"這個函數(shù)的時候气筋,它會定義一個"whisper"函數(shù),之后這個"whisper"將在"talk"里面被調(diào)用
talk()
# 輸出為:"yes..."
# 但是在"talk"這個函數(shù)的作用域之外旋圆,"whisper"這個函數(shù)是不存在的
try:
print whisper()
except NameError, e:
print e
# 輸出為: "name 'whisper' is not defined"*
函數(shù)的引用
Okay宠默,就這些東西嗎?有趣的部分該上場了...
你已經(jīng)看見灵巧,函數(shù)是對象光稼。因此,函數(shù):
- 可以賦值給其他變量
- 可以在其它函數(shù)里面定義
那意味著一個函數(shù)可以被另一個函數(shù)return
孩等。我們來看個例子! ?
def getTalk(kind="shout"):
# 我們定義了一些函數(shù)
def shout(word="yes"):
return word.capitalize()+"!"
def whisper(word="yes") :
return word.lower()+"...";
# 然后我們返回他們中的一個
if kind == "shout":
# 我們沒有用"()", 我們不是要調(diào)用這個函數(shù)
# 我們返回了這個函數(shù)對象
return shout
else:
return whisper
# 我們怎么使用它呢?
# 獲取函數(shù)艾君,并將它賦值給一個變量
talk = getTalk()
# 你可以看到在這里"talk"是一個函數(shù)對象:
print talk
# 輸出為: <function shout at 0xb7ea817c>
# 這個就是被函數(shù)返回的對象
print talk()
# 輸出為: Yes!
# 你甚至可以直接使用它:
print getTalk("whisper")()
# 輸出為: yes...
等等...這里有我們沒有注意到的地方!
既然你可以return
一個函數(shù),你就可以把一個函數(shù)當(dāng)參數(shù)傳遞:
def doSomethingBefore(func):
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
# 輸出為:
#I do something before then I call the function you gave me
#Yes!
好了肄方,你已經(jīng)具備了理解裝飾器的所有知識點冰垄。你知道,裝飾器就是 "封裝", 這意味著它可以讓你在被它裝飾的函數(shù)前面和后面執(zhí)行一些代碼,而不必改動被裝飾的函數(shù)本身虹茶。
手動創(chuàng)建裝飾器
你如何手動構(gòu)建一個裝飾器:
# 裝飾是一個函數(shù)逝薪,該函數(shù)需要另一個函數(shù)作為它的參數(shù)
def my_shiny_new_decorator(a_function_to_decorate):
# 在裝飾器的函數(shù)實現(xiàn)里面它定義了另一個函數(shù): 他就是封裝函數(shù)(wrapper)
# 這個函數(shù)將原來的函數(shù)封裝到里面
# 因此你可以在原來函數(shù)的前面和后面執(zhí)行一些附加代碼
def the_wrapper_around_the_original_function():
# 在這里放置你想在原來函數(shù)執(zhí)行前執(zhí)行的代碼
print "Before the function runs"
# 調(diào)用原來的函數(shù)(使用圓括號)
a_function_to_decorate()
# 在這里放置你想在原來函數(shù)執(zhí)行后執(zhí)行的代碼
print "After the function runs"
# 這個時候,"a_function_to_decorate"并沒有執(zhí)行
# 我們返回剛才創(chuàng)建的封裝函數(shù)
# 這個封裝函數(shù)包含了原來的函數(shù)蝴罪,和將在原來函數(shù)前面和后面執(zhí)行的代碼董济。我們就可以使用它了!
return the_wrapper_around_the_original_function
# 想象你創(chuàng)建了一個你再也不想修改的函數(shù)
def a_stand_alone_function():
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
# 輸出為: I am a stand alone function, don't you dare modify me
# 現(xiàn)在你可以裝飾這個函數(shù)來擴(kuò)展它的行為
# 只需要將這個函數(shù)傳入裝飾器,那它將被動態(tài)的包在任何你想執(zhí)行的代碼間要门,并且返回一個可被使用的新函數(shù):
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#輸出為:
#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
的時候,真正被執(zhí)行的函數(shù)是 a_stand_alone_function_decorated
欢搜。那很容易封豪,只需要使用 my_shiny_new_decorator
返回的函數(shù)賦值給原來的 a_stand_alone_function
這個函數(shù)名(其實是個變量):
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#輸出為:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# 你猜怎么著?這就是裝飾器做的事情炒瘟。
裝飾器揭秘
前面的例子吹埠,使用Python的裝飾器語法糖來重寫就是下面的樣子:
@my_shiny_new_decorator
def another_stand_alone_function():
print "Leave me alone"
another_stand_alone_function()
# 輸出為:
#Before the function runs
#Leave me alone
#After the function runs
是的,這就是全部疮装,就是這么簡單缘琅。@decorator
只是下面表達(dá)式的簡寫:
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
這里的裝飾器只是裝飾器設(shè)計模式的一種Python化變體。Python嵌入了多種經(jīng)典的設(shè)計模式來簡化開發(fā)(比如迭代器(iterators))廓推。
當(dāng)然刷袍,你可以堆積裝飾器(使用多層裝飾器):
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()
# 輸出為: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
使用Python的裝飾器語法糖:
@bread
@ingredients
def sandwich(food="--ham--"):
print food
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
你放置裝飾器的順序很重要:
@ingredients
@bread
def strange_sandwich(food="--ham--"):
print food
strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~
現(xiàn)在: 回答問題(請參考stack overflorw上的相關(guān)問題)
作為結(jié)論,你可以很容易看出如何回答問題:
# 使其變bold的裝飾器
def makebold(fn):
# 裝飾器將要返回的函數(shù)
def wrapper():
# 在原函數(shù)前面和后面插入一些代碼
return "<b>" + fn() + "</b>"
return wrapper
# 使其變italic的裝飾器
def makeitalic(fn):
# 裝飾器將要返回的函數(shù)
def wrapper():
# 在原函數(shù)前面和后面插入一些代碼
return "<i>" + fn() + "</i>"
return wrapper
@makebold
@makeitalic
def say():
return "hello"
print say()
# 輸出為: <b><i>hello</i></b>
# 這和下面代碼效果相同
def say():
return "hello"
say = makebold(makeitalic(say))
print say()
# 輸出為: <b><i>hello</i></b>
你可以高興的離開這里了受啥,或者再費點腦子來看看裝飾器的高級用法做个。
更深入的討論裝飾器
向被裝飾的函數(shù)傳參數(shù)
# 這不是黑魔法鸽心,你只需要讓封裝函數(shù)傳遞這些參數(shù):
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
# 因為當(dāng)你調(diào)用被裝飾器返回的函數(shù)時滚局,實際你是在調(diào)用封裝函數(shù)
# 所以向封裝函數(shù)傳遞參數(shù)可以讓封裝函數(shù)把參數(shù)傳遞給被裝飾的函數(shù)
@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")
# 輸出為:
# I got args! Look: Peter Venkman
# My name is Peter Venkman
裝飾方法
Python中方法和函數(shù)幾乎是一樣的,這個特性很nice顽频。唯一的不同是方法期望它的第一個參數(shù)是對當(dāng)前對象的引用(self
)藤肢。
那意味著你可以使用相同的方式來給方法添加裝飾器!只是需要將self
考慮在內(nèi):
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # 很友好吧糯景,再次減少了年齡 :-)
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 %s, what did you think?" % (self.age + lie)
l = Lucy()
l.sayYourAge(-3)
# 輸出為: I am 26, what did you think?
如果你在寫一個通用的裝飾器--可以接收任何參數(shù)的函數(shù)或者方法--這時候只需要使用 *args, **kwargs
:
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
# 封裝函數(shù)可以接收任何的參數(shù)
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print "Do I have args?:"
print args
print kwargs
# 然后你解包出參數(shù)嘁圈,這里是 *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()
#輸出為:
#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)
# 輸出為:
#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!")
# 輸出為:
#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): # 這時候你可以添加一個默認(rèn)參數(shù)值
print "I am %s, what did you think ?" % (self.age + lie)
m = Mary()
m.sayYourAge()
# 輸出為:
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?
給裝飾器傳遞參數(shù)
好了蟀淮,現(xiàn)在你覺得給裝飾器本身傳遞參數(shù)該怎么做呢?
這個可能有點繞最住,因為裝飾器必須接收一個函數(shù)作為參數(shù)。
因此怠惶,你不能把被裝飾函數(shù)的參數(shù)直接傳遞給裝飾器涨缚。
在我們說出解決辦法前,寫點代碼來找找靈感:
# 裝飾器只是普通的函數(shù)
def my_decorator(func):
print "I am an ordinary function"
def wrapper():
print "I am function returned by the decorator"
func()
return wrapper
# 因此策治,你可以不使用任何的 "@" 就可以調(diào)用它
def lazy_function():
print "zzzzzzzz"
decorated_function = my_decorator(lazy_function)
# 輸出為: I am an ordinary function
# 它輸出 "I am an ordinary function"脓魏,因為那就是你在代碼里面做的事情:
# 調(diào)用一個函數(shù)兰吟,沒有任何的魔法。
@my_decorator
def lazy_function():
print "zzzzzzzz"
# 輸出為: I am an ordinary function
上面兩種方式幾乎一樣茂翔。"my_decorator
"被調(diào)用混蔼。因此當(dāng)你在代碼里面添加 @my_decorato
時,你就在告訴Python去調(diào)用'被"my_decorator
"變量標(biāo)示的函數(shù)'珊燎。
這很重要! 你給出的這個變量名可以直接指向裝飾器-也可以不直接指向惭嚣。
我們來干點邪惡的事情。 ?
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
# 我們創(chuàng)建了一個裝飾器俐末。它就只是一個新的函數(shù)料按。
new_decorator = decorator_maker()
# 輸出為:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
# 然后我們裝飾一個函數(shù)
def decorated_function():
print "I am the decorated function."
decorated_function = new_decorator(decorated_function)
# 輸出為:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function
# 我們調(diào)用這個函數(shù):
decorated_function()
# 輸出為:
#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.
這里沒有任何驚奇的地方。
讓我們再次來做相同的事情卓箫,但是省略掉所有討厭的中間變量:
def decorated_function():
print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
# 輸出為:
#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()
# 輸出為:
#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."
# 輸出為:
#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()
# 輸出為:
#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.
Hey载矿,你注意到了嗎?我們除了 "@
"格式的語法糖外還使用了函數(shù)調(diào)用! :-)
因此烹卒,回到帶參數(shù)裝飾器的討論闷盔。如果我們可以使用函數(shù)來創(chuàng)建裝飾器,我們就可以把參數(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ù),得益于closures的特性藐吮。
# 如果你不熟悉closures,你可以假設(shè)這是沒問題的溺拱,
# 或者讀一下: 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
# 不要把裝飾器的參數(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")
# 輸出為:
#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è)定
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")
# 輸出為:
#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
正如你看到的那樣谣辞,你可以使用這個技巧像給函數(shù)傳遞參數(shù)一樣給裝飾器傳遞參數(shù)迫摔。如果你想,你甚至可以使用 *args, **kwargs
泥从。但是記住裝飾器只會被調(diào)用一次句占。僅僅當(dāng)Python載入(imports)這個腳本的時候調(diào)用。之后你不可以動態(tài)的設(shè)定參數(shù)躯嫉。當(dāng)你 "import x"的時候, 這個函數(shù)已經(jīng)被裝飾了,因此你不能再改變?nèi)魏螙|西了纱烘。
讓我們來練習(xí)一下:裝飾一個裝飾器
好了,作為福利祈餐,我將給你一個代碼片段,它可以讓裝飾器接收任何參數(shù)擂啥。
為了能接收參數(shù),我們使用另一個函數(shù)來創(chuàng)建我們的裝飾器帆阳。
我們封裝了裝飾器哺壶。
我們最近知道的能封裝函數(shù)的東西是什么?
對,就是裝飾器
讓我們來寫一個裝飾裝飾器的裝飾器來玩玩(夠繞吧):
def decorator_with_args(decorator_to_enhance):
"""
這個函數(shù)被當(dāng)做裝飾器來使用变骡。
它必須裝飾另一個函數(shù)离赫,這個函數(shù)也被當(dāng)做裝飾器使用
感覺理解不過來,休息一下
它允許任何的裝飾器接收任何參數(shù)塌碌。
在你的腦子里面記住每次應(yīng)該怎樣構(gòu)建這樣的裝飾器
"""
# 我們使用相同的技巧來傳遞參數(shù)
def decorator_maker(*args, **kwargs):
# 我們動態(tài)的創(chuàng)建一個接收一個函數(shù)作為參數(shù)的裝飾器
# 保持住從decorator_maker傳遞過來的參數(shù)渊胸。
def decorator_wrapper(func):
# 我們最后返回原始裝飾器的結(jié)果,
# 這個結(jié)果就是最原始被裝飾的函數(shù) (就是返回一個函數(shù))台妆。
# 這里只有一個小缺陷:被封裝的裝飾器必須具有特定的簽名翎猛,不然它不會工作
return decorator_to_enhance(func, *args, **kwargs)這個樣子
return decorator_wrapper
return decorator_maker
它可以像下面這樣使用:
# 你創(chuàng)建一個將要被用作裝飾器的函數(shù)。并向它添加一個裝飾器(這里是指decorator_with_args) :-)
# 不要忘了你創(chuàng)建的這個裝飾器簽名必須是 "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)來裝飾一個函數(shù)
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print "Hello", function_arg1, function_arg2
decorated_function("Universe and", "everything")
# 輸出為:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything
# 喔!
我知道接剩,你有了這樣一種感覺切厘,它是在聽一一個家伙說:"before understanding recursion, you must first understand recursion"產(chǎn)生的。但是懊缺,現(xiàn)在疫稿,你是否有更好的體會了?
最佳實踐: 裝飾器
- 裝飾器是Python2.4引入的鹃两,因此保證你的代碼運行的版本 >= 2.4遗座。
- 裝飾器會拖慢函數(shù)的執(zhí)行速度,記住這點。
- 你不能反裝飾一個函數(shù)俊扳。因此一旦一個函數(shù)被裝飾了途蒋,它對所有其它代碼來說就都是被裝飾了的。
- 裝飾器會封裝函數(shù)馋记,這會讓它們變得更難調(diào)試号坡。 (這點從Python >= 2.5版本變得好了起來; 具體參考下面。)
在Python 2.5中 functools
模塊被引入梯醒。它包含了 functools.wraps()
函數(shù)宽堆,這個函數(shù)會將被裝飾函數(shù)的名稱、模塊冤馏、文檔字符串拷貝到封裝函數(shù)日麸。
(有趣的事實是: functools.wraps()
也是一個裝飾器! ?)
# 為了調(diào)試寄啼,在堆棧軌跡中打印了函數(shù)的名稱(__name__)
def foo():
print "foo"
print foo.__name__
# 輸出為 : foo
# 有了裝飾器逮光,名稱就凌亂了
def bar(func):
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
# 輸出為: wrapper
# "functools"對這個有幫助
import functools
def bar(func):
# 我們看到"wrapper"封裝了"func"
# 現(xiàn)在魔法開始了
@functools.wraps(func)
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
# 輸出為: foo
裝飾器可以被用在什么地方?
現(xiàn)在問題來了:我可以使用裝飾器來干什么?
盡管看起來很酷很強(qiáng)大,但是一個實際的應(yīng)用事例能更好的說明問題墩划。好了涕刚,這里有1000種可能性。經(jīng)典的使用是在庫以外的代碼中擴(kuò)展一個函數(shù)的行為 (你不能修改的函數(shù))乙帮,或者只是為了調(diào)試 (因為調(diào)試只是零時的杜漠,所有你不想修改這個函數(shù))。
你可以以一種DRY的方式使用他們來擴(kuò)展許多函數(shù),像這樣:
def benchmark(func):
"""
打印原函數(shù)調(diào)用時間的裝飾器
"""
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):
"""
記錄并打印一個函數(shù)執(zhí)行次數(shù)的裝飾器
"""
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!")
#輸出為:
#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:
@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使用裝飾器來管理緩存和視圖權(quán)限击碗。
- Twisted to fake inlining asynchronous functions calls.(對Twisted不熟,不知道具體講的是神馬们拙!)稍途。
這個真是一個巨大的游樂場