裝飾器(Decorator)是Python的一個重要部分烧栋。簡單地說:它們是修改其它函數(shù)的功能的函數(shù)。
它們有助于讓我們的代碼更簡短珍特,也更Pythonic(Python范兒)魔吐。大多數(shù)初學(xué)者不知道在哪兒使用它們酬姆,所以我將要分享下,哪些區(qū)域里裝飾器可以讓你的代碼更簡潔骨宠。
1 一切皆對象
首先我們來理解下Python中的函數(shù)相满。
def hi(name="Jack"):
return "hi " + name
print(hi()) # output: 'hi Jack'
我們甚至可以將一個函數(shù)賦值給一個變量,比如:
def hi(name="Jack"):
return "hi " + name
greet = hi
print(greet()) # output: 'hi Jack'
我們這里沒有在使用小括號匿又,因為我們并不是在調(diào)用hi函數(shù)碌更,而是在將它放在greet變量里頭躲撰。我們嘗試運行下這個:
# 如果我們刪掉舊的hi函數(shù),看看會發(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ù)的基本知識了谆棱,更進一步垃瞧,在Python中我們可以在一個函
數(shù)中定義另一個函數(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
上面展示了無論何時你調(diào)用hi(), greet()和welcome()將會同時被調(diào)用。然后greet()和welcome()函數(shù)在hi()函數(shù)之外是不能訪問的脉幢,比如:
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ù)嫌松。也就是說:我們可以創(chuàng)建嵌套的函數(shù)。現(xiàn)在你需要再多學(xué)一點液走,就是函數(shù)也能返回函數(shù)贾陷。
3 從函數(shù)中返回函數(shù)
其實并不需要在一個函數(shù)里去執(zhí)行另一個函數(shù),我們也可以將其作為輸出返回出來:
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ù)巷懈。
再次看看這個代碼砸喻。在if/else語句中我們返回greet和welcome蒋譬,而不是greet()和welcome()。為什么那樣癣漆?這是因為當你把一對小括號放在后面剂买,這個函數(shù)就會執(zhí)行瞬哼;然而如果你不放括號在它后面,那它可以被到處傳遞较性,并且可以賦值給別的變量而不去執(zhí)行它结胀。
你明白了嗎?讓我再稍微多解釋點細節(jié)攀操。
當我們寫下a = hi()秸抚,hi()會被執(zhí)行,而由于name參數(shù)默認是Jack颠放,所以函數(shù)greet被返回了慈迈。如果我們把語句改為a = hi(name = "ali")省有,那么welcome函數(shù)將被返回。此時打印出hi()()伸头,這會輸出now you are in the welcome() function舷蟀。
4 將函數(shù)作為參數(shù)傳給另一個函數(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)具備所有必需知識野宜,來進一步學(xué)習(xí)裝飾器真正是什么了。裝飾器讓你在一個函數(shù)的前后去執(zhí)行代碼河胎。
5 你的第一個裝飾器
在上一個例子里虎敦,其實我們已經(jīng)創(chuàng)建了一個裝飾器其徙!現(xià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中裝飾器做的事情昌罩!它們封裝一個函數(shù),并且用這樣或那樣的方式來修改它的行為∏沧埽現(xiàn)在你也許疑惑,我們在代碼里并沒有使用@符號容达?那只是一個簡短的方式來生成一個被裝飾的函數(shù)垂券。
這里是我們?nèi)绾问褂聾來運行之前的代碼:
@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()
# 實際上菇爪,@a_new_decorator是以下用法的簡寫:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
希望你現(xiàn)在對Python裝飾器的工作原理有一個基本的理解。如果我們運行如下代碼會存在一個問題:
print(a_function_requiring_decoration.__name__)
# output: wrapTheFunction
這并不是我們想要的熙揍!Ouput輸出應(yīng)該a_function_requiring_decoration届囚。這里的函數(shù)被warpTheFunction替代了是尖。它重寫了我們函數(shù)的名字和注釋文檔(docstring)。
幸運的是Python提供給我們一個簡單的函數(shù)來解決這個問題昔字,那就是functools.wraps首繁。我們修改上一個例子來使用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)在好多了弦疮。我們接下來學(xué)習(xí)裝飾器的一些常用場景胁塞。
以下是藍本規(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接受一個函數(shù)來進行裝飾,并加入了復(fù)制函數(shù)名稱编检、注釋文檔扰才、參數(shù)列表等等的功能衩匣。這可以讓我們在裝飾器里面訪問在裝飾之前的函數(shù)的屬性粥航。
6 使用場景
現(xiàn)在我們來看一下裝飾器在哪些地方特別耀眼生百,以及使用它可以讓一些事情管理起來變得更簡單蚀浆。
6.1 授權(quán)Authorization
裝飾器能有助于檢查某個人是否被授權(quán)去使用一個web應(yīng)用的端點(endpoint)。它們被大量使用于Flask和Django web框架中杠输。這里是一個例子來使用基于裝飾器的授權(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
日志是裝飾器運用的另一個亮點。這是個例子:
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)在思考裝飾器的一個其他聰明用法了据忘。
7 帶參數(shù)的裝飾器
來想想這個問題搞糕,難道@wraps不也是個裝飾器嗎?但是汉规,它接收一個參數(shù)驹吮,就像任何普通的函數(shù)能做的那樣碟狞。那么,為什么我們不也那樣做呢频祝?
這是因為脆淹,當你使用@my_decorator語法時盖溺,你是在應(yīng)用一個以單個函數(shù)作為參數(shù)的一個包裹函數(shù)。記住兼蜈,Python里每個東西都是一個對象,而且包括函數(shù)歼郭!記住了這些辐棒,我們可以編寫一下能返回一個包裹函數(shù)的函數(shù)。
7.1 在函數(shù)中嵌入裝飾器
我們回到日志的例子泰涂,并創(chuàng)建一個包裹函數(shù)逼蒙,能讓我們指定一個用于輸出的日志文件寄疏。
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
# 打開代碼所在的工作目錄陕截,會發(fā)現(xiàn)新建了一個名為out.log,此log的內(nèi)容為:myfunc1 was called
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# output: myfunc2 was called
# 打開代碼所在的工作目錄社搅,會發(fā)現(xiàn)新建了一個名為func2.log乳规,此log的內(nèi)容為:myfunc2 was called
7.2 裝飾器類
現(xiàn)在我們有了能用于正式環(huán)境的logit裝飾器暮的,但當我們的應(yīng)用的某些部分還比較脆弱時,異常也許是需要更緊急關(guān)注的事情源织。比方說有時你只想打日志到一個文件微猖。而有時你想把引起你注意的問題發(fā)送到一個email凛剥,同時也保留日志,留個記錄逻炊。這是一個使用繼承的場景,但目前為止我們只看到過用來構(gòu)建裝飾器的函數(shù)豹休。
幸運的是桨吊,類也可以用來構(gòu)建裝飾器视乐。那我們現(xiàn)在以一個類而不是一個函數(shù)的方式,來重新構(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
這個實現(xiàn)有一個附加優(yōu)勢独榴,在于比嵌套函數(shù)的方式更加整潔奕枝,而且包裹一個函數(shù)還是使用跟以前一樣的語法:
@logit()
def myfunc1():
pass
現(xiàn)在瓶堕,我們給logit創(chuàng)建子類郎笆,來添加email的功能(雖然email這個話題不會在這里展開)。
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激捏,這里就不做實現(xiàn)了
pass
從現(xiàn)在起远舅,@email_logit將會和@logit產(chǎn)生同樣的效果痕钢,但是在打日志的基礎(chǔ)上任连,還會多發(fā)送一封郵件給管理員。