裝飾器(Decorator)是Python的一個(gè)重要部分。簡(jiǎn)單地說(shuō):它們是修改其它函數(shù)的功能的函數(shù)。
它們有助于讓我們的代碼更簡(jiǎn)短,也更Pythonic(Python范兒)。大多數(shù)初學(xué)者不知道在哪兒使用它們,所以我將要分享下,哪些區(qū)域里裝飾器可以讓你的代碼更簡(jiǎn)潔。
1 一切皆對(duì)象
首先我們來(lái)理解下Python中的函數(shù)。
def hi(name="Jack"):
return "hi " + name
print(hi()) # output: 'hi Jack'
我們甚至可以將一個(gè)函數(shù)賦值給一個(gè)變量,比如:
def hi(name="Jack"):
return "hi " + name
greet = hi
print(greet()) # output: 'hi Jack'
我們這里沒(méi)有在使用小括號(hào),因?yàn)槲覀儾⒉皇窃谡{(diào)用hi函數(shù),而是在將它放在greet變量里頭。我們嘗試運(yùn)行下這個(gè):
# 如果我們刪掉舊的hi函數(shù),看看會(huì)發(fā)生什么!
def hi(name="Jack"):
return "hi " + name
greet = hi
del hi
print(hi()) # output: NameError: name 'hi' is not defined
print(greet()) # output: 'hi Jack'
2 在函數(shù)中定義函數(shù)
剛才那些就是函數(shù)的基本知識(shí)了,更進(jìn)一步,在Python中我們可以在一個(gè)函
數(shù)中定義另一個(gè)函數(shù):
def hi(name="Jack"):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print(greet())
print(welcome())
print("now you are back in the hi() function")
hi()
# 輸出為:
now you are inside the hi() function
now you are in the greet() function
now you are in the welcome() function
now you are back in the hi() function
上面展示了無(wú)論何時(shí)你調(diào)用hi(), greet()和welcome()將會(huì)同時(shí)被調(diào)用。然后greet()和welcome()函數(shù)在hi()函數(shù)之外是不能訪問(wèn)的,比如:
greet()
# 輸出為:
NameError Traceback (most recent call last)
<ipython-input-2-23aff5db816b> in <module>()
----> 1 greet()
NameError: name 'greet' is not defined
那現(xiàn)在我們知道了可以在函數(shù)中定義另外的函數(shù)。也就是說(shuō):我們可以創(chuàng)建嵌套的函數(shù)?,F(xiàn)在你需要再多學(xué)一點(diǎn),就是函數(shù)也能返回函數(shù)。
3 從函數(shù)中返回函數(shù)
其實(shí)并不需要在一個(gè)函數(shù)里去執(zhí)行另一個(gè)函數(shù),我們也可以將其作為輸出返回出來(lái):
def hi(name="Jack"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "Jack":
return greet
else:
return welcome
a = hi()
print(a) # output: <function greet at 0x0000000005894048>
print(a()) # output: now you are in the greet() function
上面清晰地展示了a現(xiàn)在指向到hi()函數(shù)中的greet()函數(shù)。
再次看看這個(gè)代碼。在if/else語(yǔ)句中我們返回greet和welcome,而不是greet()和welcome()。為什么那樣?這是因?yàn)楫?dāng)你把一對(duì)小括號(hào)放在后面,這個(gè)函數(shù)就會(huì)執(zhí)行;然而如果你不放括號(hào)在它后面,那它可以被到處傳遞,并且可以賦值給別的變量而不去執(zhí)行它。
你明白了嗎?讓我再稍微多解釋點(diǎn)細(xì)節(jié)。
當(dāng)我們寫(xiě)下a = hi(),hi()會(huì)被執(zhí)行,而由于name參數(shù)默認(rèn)是Jack,所以函數(shù)greet被返回了。如果我們把語(yǔ)句改為a = hi(name = "ali"),那么welcome函數(shù)將被返回。此時(shí)打印出hi()(),這會(huì)輸出now you are in the welcome() function。
4 將函數(shù)作為參數(shù)傳給另一個(gè)函數(shù)
def hi():
return "hi Jack!"
def doSomethingBeforeHi(func):
print("I'm doing some exciting work before executing hi()")
print(func())
doSomethingBeforeHi(hi)
# 輸出為:
I'm doing some exciting work before executing hi()
hi Jack!
現(xiàn)在你已經(jīng)具備所有必需知識(shí),來(lái)進(jìn)一步學(xué)習(xí)裝飾器真正是什么了。裝飾器讓你在一個(gè)函數(shù)的前后去執(zhí)行代碼。
5 你的第一個(gè)裝飾器
在上一個(gè)例子里,其實(shí)我們已經(jīng)創(chuàng)建了一個(gè)裝飾器!現(xiàn)在我們修改下上一個(gè)裝飾器,并編寫(xiě)一個(gè)稍微更有用點(diǎn)的程序:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I'm doing some exciting work before executing a_func()")
a_func()
print("I'm doing some exciting work after executing a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I'm the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
# 輸出為:
I'm the function which needs some decoration to remove my foul smell
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
# 輸出為:
I'm doing some exciting work before executing a_func()
I'm the function which needs some decoration to remove my foul smell
I'm doing some exciting work after executing a_func()
你看明白了嗎?我們剛剛應(yīng)用了之前學(xué)習(xí)到的原理。這正是python中裝飾器做的事情!它們封裝一個(gè)函數(shù),并且用這樣或那樣的方式來(lái)修改它的行為?,F(xiàn)在你也許疑惑,我們?cè)诖a里并沒(méi)有使用@符號(hào)?那只是一個(gè)簡(jiǎn)短的方式來(lái)生成一個(gè)被裝飾的函數(shù)。
這里是我們?nèi)绾问褂聾來(lái)運(yùn)行之前的代碼:
@a_new_decorator
def a_function_requiring_decoration():
print("I'm the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
# 輸出為:
I'm doing some exciting work before executing a_func()
I'm the function which needs some decoration to remove my foul smell
I'm doing some exciting work after executing a_func()
# 實(shí)際上,@a_new_decorator是以下用法的簡(jiǎn)寫(xiě):
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
希望你現(xiàn)在對(duì)Python裝飾器的工作原理有一個(gè)基本的理解。如果我們運(yùn)行如下代碼會(huì)存在一個(gè)問(wèn)題:
print(a_function_requiring_decoration.__name__)
# output: wrapTheFunction
這并不是我們想要的!Ouput輸出應(yīng)該a_function_requiring_decoration。這里的函數(shù)被warpTheFunction替代了。它重寫(xiě)了我們函數(shù)的名字和注釋文檔(docstring)。
幸運(yùn)的是Python提供給我們一個(gè)簡(jiǎn)單的函數(shù)來(lái)解決這個(gè)問(wèn)題,那就是functools.wraps。我們修改上一個(gè)例子來(lái)使用functools.wraps:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I'm doing some excting work before executing a_func()")
a_func()
print("I'm doing some exciting work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
print(a_function_requiring_decoration.__name__)
# output: a_function_requiring_decoration
現(xiàn)在好多了。我們接下來(lái)學(xué)習(xí)裝飾器的一些常用場(chǎng)景。
以下是藍(lán)本規(guī)范:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# output: Function is running
can_run = False
print(func())
# output: Function will not run
注意:@wraps接受一個(gè)函數(shù)來(lái)進(jìn)行裝飾,并加入了復(fù)制函數(shù)名稱(chēng)、注釋文檔、參數(shù)列表等等的功能。這可以讓我們?cè)谘b飾器里面訪問(wèn)在裝飾之前的函數(shù)的屬性。
6 使用場(chǎng)景
現(xiàn)在我們來(lái)看一下裝飾器在哪些地方特別耀眼,以及使用它可以讓一些事情管理起來(lái)變得更簡(jiǎn)單。
6.1 授權(quán)Authorization
裝飾器能有助于檢查某個(gè)人是否被授權(quán)去使用一個(gè)web應(yīng)用的端點(diǎn)(endpoint)。它們被大量使用于Flask和Django web框架中。這里是一個(gè)例子來(lái)使用基于裝飾器的授權(quán):
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
6.2 日志Logging
日志是裝飾器運(yùn)用的另一個(gè)亮點(diǎn)。這是個(gè)例子:
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
return x + x
result = addition_func(4)
# output: addition_func was called
我敢肯定你已經(jīng)在思考裝飾器的一個(gè)其他聰明用法了。
7 帶參數(shù)的裝飾器
來(lái)想想這個(gè)問(wèn)題,難道@wraps不也是個(gè)裝飾器嗎?但是,它接收一個(gè)參數(shù),就像任何普通的函數(shù)能做的那樣。那么,為什么我們不也那樣做呢?
這是因?yàn)?,?dāng)你使用@my_decorator語(yǔ)法時(shí),你是在應(yīng)用一個(gè)以單個(gè)函數(shù)作為參數(shù)的一個(gè)包裹函數(shù)。記住,Python里每個(gè)東西都是一個(gè)對(duì)象,而且包括函數(shù)!記住了這些,我們可以編寫(xiě)一下能返回一個(gè)包裹函數(shù)的函數(shù)。
7.1 在函數(shù)中嵌入裝飾器
我們回到日志的例子,并創(chuàng)建一個(gè)包裹函數(shù),能讓我們指定一個(gè)用于輸出的日志文件。
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(logfile, 'a') as opened_file:
opened_file.write(log_string + '\n')
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# output: myfunc1 was called
# 打開(kāi)代碼所在的工作目錄,會(huì)發(fā)現(xiàn)新建了一個(gè)名為out.log,此log的內(nèi)容為:myfunc1 was called
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# output: myfunc2 was called
# 打開(kāi)代碼所在的工作目錄,會(huì)發(fā)現(xiàn)新建了一個(gè)名為func2.log,此log的內(nèi)容為:myfunc2 was called
7.2 裝飾器類(lèi)
現(xiàn)在我們有了能用于正式環(huán)境的logit裝飾器,但當(dāng)我們的應(yīng)用的某些部分還比較脆弱時(shí),異常也許是需要更緊急關(guān)注的事情。比方說(shuō)有時(shí)你只想打日志到一個(gè)文件。而有時(shí)你想把引起你注意的問(wèn)題發(fā)送到一個(gè)email,同時(shí)也保留日志,留個(gè)記錄。這是一個(gè)使用繼承的場(chǎng)景,但目前為止我們只看到過(guò)用來(lái)構(gòu)建裝飾器的函數(shù)。
幸運(yùn)的是,類(lèi)也可以用來(lái)構(gòu)建裝飾器。那我們現(xiàn)在以一個(gè)類(lèi)而不是一個(gè)函數(shù)的方式,來(lái)重新構(gòu)建logit。
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
log_string = func.__name__ + " was called"
print(log_string)
with open(self.logfile, 'a') as opened_file:
opened_file.write(log_string + '\n')
# Now, send a notification
self.notify()
def notify(self):
pass
這個(gè)實(shí)現(xiàn)有一個(gè)附加優(yōu)勢(shì),在于比嵌套函數(shù)的方式更加整潔,而且包裹一個(gè)函數(shù)還是使用跟以前一樣的語(yǔ)法:
@logit()
def myfunc1():
pass
現(xiàn)在,我們給logit創(chuàng)建子類(lèi),來(lái)添加email的功能(雖然email這個(gè)話題不會(huì)在這里展開(kāi))。
class email_logit(logit):
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 發(fā)送一封email到self.email,這里就不做實(shí)現(xiàn)了
pass
從現(xiàn)在起,@email_logit將會(huì)和@logit產(chǎn)生同樣的效果,但是在打日志的基礎(chǔ)上,還會(huì)多發(fā)送一封郵件給管理員。