上一篇文章為:→1.1.6閉包
裝飾器
裝飾器是程序開(kāi)發(fā)中經(jīng)常會(huì)用到的一個(gè)功能彤灶,用好了裝飾器看幼,開(kāi)發(fā)效率如虎添翼,所以這也是Python面試中必問(wèn)的問(wèn)題幌陕,但對(duì)于好多初次接觸這個(gè)知識(shí)的人來(lái)講诵姜,這個(gè)功能有點(diǎn)繞,自學(xué)時(shí)直接繞過(guò)去了搏熄,然后面試問(wèn)到了就掛了棚唆,因?yàn)檠b飾器是程序開(kāi)發(fā)的基礎(chǔ)知識(shí),這個(gè)都不會(huì)搬卒,別跟人家說(shuō)你會(huì)Python, 看了下面的文章瑟俭,保證你學(xué)會(huì)裝飾器。
1契邀、先明白這段代碼
#### 第一波 ####
def foo():
print('foo')
foo #表示是函數(shù)
foo() #表示執(zhí)行foo函數(shù)
#### 第二波 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() # 執(zhí)行下面的lambda表達(dá)式摆寄,而不再是原來(lái)的foo函數(shù),因?yàn)閒oo這個(gè)名字被重新指向了另外一個(gè)匿名函數(shù)
2坯门、需求來(lái)了
初創(chuàng)公司有N個(gè)業(yè)務(wù)部門微饥,1個(gè)基礎(chǔ)平臺(tái)部門,基礎(chǔ)平臺(tái)負(fù)責(zé)提供底層的功能古戴,如:數(shù)據(jù)庫(kù)操作欠橘、redis調(diào)用、監(jiān)控API等功能现恼。業(yè)務(wù)部門使用基礎(chǔ)功能時(shí)肃续,只需調(diào)用基礎(chǔ)平臺(tái)提供的功能即可。如下:
############### 基礎(chǔ)平臺(tái)提供的功能如下 ###############
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
def f4():
print('f4')
############### 業(yè)務(wù)部門A 調(diào)用基礎(chǔ)平臺(tái)提供的功能 ###############
f1()
f2()
f3()
f4()
############### 業(yè)務(wù)部門B 調(diào)用基礎(chǔ)平臺(tái)提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有條不紊的進(jìn)行著叉袍,但是始锚,以前基礎(chǔ)平臺(tái)的開(kāi)發(fā)人員在寫代碼時(shí)候沒(méi)有關(guān)注驗(yàn)證相關(guān)的問(wèn)題,即:基礎(chǔ)平臺(tái)的提供的功能可以被任何人使用≡洌現(xiàn)在需要對(duì)基礎(chǔ)平臺(tái)的所有功能進(jìn)行重構(gòu)瞧捌,為平臺(tái)提供的所有功能添加驗(yàn)證機(jī)制,即:執(zhí)行功能前润文,先進(jìn)行驗(yàn)證姐呐。
老大把工作交給 Low B,他是這么做的:
跟每個(gè)業(yè)務(wù)部門交涉典蝌,每個(gè)業(yè)務(wù)部門自己寫代碼曙砂,調(diào)用基礎(chǔ)平臺(tái)的功能之前先驗(yàn)證。誒骏掀,這樣一來(lái)基礎(chǔ)平臺(tái)就不需要做任何修改了调炬。太棒了,有充足的時(shí)間泡妹子...
當(dāng)天Low B 被開(kāi)除了…
老大把工作交給 Low BB米绕,他是這么做的:
############### 基礎(chǔ)平臺(tái)提供的功能如下 ###############
def f1():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f1')
def f2():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f2')
def f3():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f3')
def f4():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f4')
############### 業(yè)務(wù)部門不變 ###############
### 業(yè)務(wù)部門A 調(diào)用基礎(chǔ)平臺(tái)提供的功能###
f1()
f2()
f3()
f4()
### 業(yè)務(wù)部門B 調(diào)用基礎(chǔ)平臺(tái)提供的功能 ###
f1()
f2()
f3()
f4()
過(guò)了一周 Low BB 被開(kāi)除了…
老大把工作交給 Low BBB娇哆,他是這么做的:
只對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行重構(gòu),其他業(yè)務(wù)部門無(wú)需做任何修改
############### 基礎(chǔ)平臺(tái)提供的功能如下 ###############
def check_login():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
pass
def f1():
check_login()
print('f1')
def f2():
check_login()
print('f2')
def f3():
check_login()
print('f3')
def f4():
check_login()
print('f4')
老大看了下Low BBB 的實(shí)現(xiàn)侧纯,嘴角漏出了一絲的欣慰的笑新锈,語(yǔ)重心長(zhǎng)的跟Low BBB聊了個(gè)天:
老大說(shuō):
寫代碼要遵循開(kāi)放封閉
原則,雖然在這個(gè)原則是用的面向?qū)ο箝_(kāi)發(fā)眶熬,但是也適用于函數(shù)式編程妹笆,簡(jiǎn)單來(lái)說(shuō),它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改娜氏,但可以被擴(kuò)展拳缠,即:
封閉:已實(shí)現(xiàn)的功能代碼塊
開(kāi)放:對(duì)擴(kuò)展開(kāi)發(fā)
如果將開(kāi)放封閉原則應(yīng)用在上述需求中,那么就不允許在函數(shù) f1 贸弥、f2窟坐、f3、f4的內(nèi)部進(jìn)行修改代碼绵疲,老板就給了Low BBB一個(gè)實(shí)現(xiàn)方案:
def w1(func):
def inner():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
對(duì)于上述代碼哲鸳,也是僅僅對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行修改,就可以實(shí)現(xiàn)在其他人調(diào)用函數(shù) f1 f2 f3 f4 之前都進(jìn)行【驗(yàn)證】操作盔憨,并且其他業(yè)務(wù)部門無(wú)需做任何操作徙菠。
Low BBB心驚膽戰(zhàn)的問(wèn)了下,這段代碼的內(nèi)部執(zhí)行原理是什么呢郁岩?
老大正要生氣婿奔,突然Low BBB的手機(jī)掉到地上,恰巧屏保就是Low BBB的女友照片问慎,老大一看一緊一抖萍摊,喜笑顏開(kāi),決定和Low BBB交個(gè)好朋友蝴乔。
詳細(xì)的開(kāi)始講解了:
單獨(dú)以f1為例:
def w1(func):
def inner():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
func()
return inner
@w1
def f1():
print('f1')
python解釋器就會(huì)從上到下解釋代碼记餐,步驟如下:
def w1(func): ==>將w1函數(shù)加載到內(nèi)存
@w1
沒(méi)錯(cuò), 從表面上看解釋器僅僅會(huì)解釋這兩句代碼薇正,因?yàn)楹瘮?shù)在 沒(méi)有被調(diào)用之前其內(nèi)部代碼不會(huì)被執(zhí)行片酝。
從表面上看解釋器著實(shí)會(huì)執(zhí)行這兩句,但是 @w1 這一句代碼里卻有大文章挖腰, @函數(shù)名 是python的一種語(yǔ)法糖雕沿。
上例@w1內(nèi)部會(huì)執(zhí)行一下操作:
執(zhí)行w1函數(shù)
執(zhí)行w1函數(shù) ,并將 @w1 下面的函數(shù)作為w1函數(shù)的參數(shù)猴仑,即:@w1 等價(jià)于 w1(f1) 所以审轮,內(nèi)部就會(huì)去執(zhí)行:
def inner():
#驗(yàn)證 1
#驗(yàn)證 2
#驗(yàn)證 3
f1() # func是參數(shù)肥哎,此時(shí) func 等于 f1
return inner# 返回的 inner,inner代表的是函數(shù)疾渣,非執(zhí)行函數(shù) ,其實(shí)就是將原來(lái)的 f1 函數(shù)塞進(jìn)另外一個(gè)函數(shù)中
w1的返回值
將執(zhí)行完的w1函數(shù)返回值 賦值 給@w1下面的函數(shù)的函數(shù)名f1 即將w1的返回值再重新賦值給 f1篡诽,即:
新f1 = def inner():
#驗(yàn)證 1
#驗(yàn)證 2
#驗(yàn)證 3
原來(lái)f1()
return inner
所以,以后業(yè)務(wù)部門想要執(zhí)行 f1 函數(shù)時(shí)榴捡,就會(huì)執(zhí)行 新f1 函數(shù)杈女,在新f1 函數(shù)內(nèi)部先執(zhí)行驗(yàn)證,再執(zhí)行原來(lái)的f1函數(shù)吊圾,然后將原來(lái)f1 函數(shù)的返回值返回給了業(yè)務(wù)調(diào)用者达椰。
如此一來(lái), 即執(zhí)行了驗(yàn)證的功能项乒,又執(zhí)行了原來(lái)f1函數(shù)的內(nèi)容啰劲,并將原f1函數(shù)返回值 返回給業(yè)務(wù)調(diào)用著
Low BBB 你明白了嗎?要是沒(méi)明白的話檀何,我晚上去你家?guī)湍憬鉀Q吧S恪!埃碱!
3. 再議裝飾器
#定義函數(shù):完成包裹數(shù)據(jù)
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
#定義函數(shù):完成包裹數(shù)據(jù)
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
print(test1()))
print(test2()))
print(test3()))
運(yùn)行結(jié)果:
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
4. 裝飾器(decorator)功能
1.引入日志
2.函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)
3.執(zhí)行函數(shù)前預(yù)備處理
4.執(zhí)行函數(shù)后清理功能
5.權(quán)限校驗(yàn)等場(chǎng)景
6.緩存
5. 裝飾器示例
例1:無(wú)參數(shù)的函數(shù)
from time import ctime, sleep
def timefun(func):
def wrappedfunc():
print("%s called at %s"%(func.__name__, ctime()))
func()
return wrappedfunc
@timefun
def foo():
print("I am foo")
foo()
sleep(2)
foo()
上面代碼理解裝飾器執(zhí)行行為可理解成
foo = timefun(foo)
#foo先作為參數(shù)賦值給func后,foo接收指向timefun返回的wrappedfunc
foo()
#調(diào)用foo(),即等價(jià)調(diào)用wrappedfunc()
#內(nèi)部函數(shù)wrappedfunc被引用猖辫,所以外部函數(shù)的func變量(自由變量)并沒(méi)有釋放
#func里保存的是原foo函數(shù)對(duì)象
例2:被裝飾的函數(shù)有參數(shù)
from time import ctime, sleep
def timefun(func):
def wrappedfunc(a, b):
print("%s called at %s"%(func.__name__, ctime()))
print(a, b)
func(a, b)
return wrappedfunc
@timefun
def foo(a, b):
print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
例3:被裝飾的函數(shù)有不定長(zhǎng)參數(shù)
from time import ctime, sleep
def timefun(func):
def wrappedfunc(*args, **kwargs):
print("%s called at %s"%(func.__name__, ctime()))
func(*args, **kwargs)
return wrappedfunc
@timefun
def foo(a, b, c):
print(a+b+c)
foo(3,5,7)
sleep(2)
foo(2,4,9)
例4:裝飾器中的return
from time import ctime, sleep
def timefun(func):
def wrappedfunc():
print("%s called at %s"%(func.__name__, ctime()))
func()
return wrappedfunc
@timefun
def foo():
print("I am foo")
@timefun
def getInfo():
return '----hahah---'
foo()
sleep(2)
foo()
print(getInfo())
執(zhí)行結(jié)果:
foo called at Fri Nov 4 21:55:35 2016
I am foo
foo called at Fri Nov 4 21:55:37 2016
I am foo
getInfo called at Fri Nov 4 21:55:37 2016
None
如果修改裝飾器為return func()
,則運(yùn)行結(jié)果:
foo called at Fri Nov 4 21:55:57 2016
I am foo
foo called at Fri Nov 4 21:55:59 2016
I am foo
getInfo called at Fri Nov 4 21:55:59 2016
----hahah---
總結(jié):
- 一般情況下為了讓裝飾器更通用砚殿,可以有return
例5:裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上啃憎,設(shè)置外部變量
#decorator2.py
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrappedfunc():
print("%s called at %s %s"%(func.__name__, ctime(), pre))
return func()
return wrappedfunc
return timefun
@timefun_arg("itcast")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
可以理解為
foo()==timefun_arg("itcast")(foo)()
例6:類裝飾器(擴(kuò)展,非重點(diǎn))
裝飾器函數(shù)其實(shí)是這樣一個(gè)接口約束似炎,它必須接受一個(gè)callable對(duì)象作為參數(shù)辛萍,然后返回一個(gè)callable對(duì)象。在Python中一般callable對(duì)象都是函數(shù)羡藐,但也有例外贩毕。只要某個(gè)對(duì)象重寫了 __call__()
方法,那么這個(gè)對(duì)象就是callable的仆嗦。
class Test():
def __call__(self):
print('call me!')
t = Test()
t() # call me
類裝飾器demo
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---裝飾器中的功能---")
self.__func()
#說(shuō)明:
#1. 當(dāng)用Test來(lái)裝作裝飾器對(duì)test函數(shù)進(jìn)行裝飾的時(shí)候辉阶,首先會(huì)創(chuàng)建Test的實(shí)例對(duì)象
# 并且會(huì)把test這個(gè)函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中
# 即在__init__方法中的func變量指向了test函數(shù)體
#
#2. test函數(shù)相當(dāng)于指向了用Test創(chuàng)建出來(lái)的實(shí)例對(duì)象
#
#3. 當(dāng)在使用test()進(jìn)行調(diào)用時(shí),就相當(dāng)于讓這個(gè)對(duì)象()瘩扼,因此會(huì)調(diào)用這個(gè)對(duì)象的__call__方法
#
#4. 為了能夠在__call__方法中調(diào)用原來(lái)test指向的函數(shù)體谆甜,所以在__init__方法中就需要一個(gè)實(shí)例屬性來(lái)保存這個(gè)函數(shù)體的引用
# 所以才有了self.__func = func這句代碼,從而在調(diào)用__call__方法中能夠調(diào)用到test之前的函數(shù)體
@Test
def test():
print("----test---")
test()
showpy()#如果把這句話注釋集绰,重新運(yùn)行程序规辱,依然會(huì)看到"--初始化--"
運(yùn)行結(jié)果如下:
---初始化---
func name is test
---裝飾器中的功能---
----test---