說到裝飾器,很明顯就是用來裝飾的,既然是要裝飾,那肯定是在保留原有的基礎(chǔ)上再添加一些東西作為裝飾,這就是我對(duì)裝飾器最直白的理解。
那么如何去學(xué)習(xí)這個(gè)裝飾器呢?這個(gè)裝飾器又是咋回事?
裝飾器的幾個(gè)特點(diǎn)
首先我們需要先記住裝飾器的幾個(gè)特點(diǎn):
- 裝飾器本質(zhì)上也是一個(gè)函數(shù) ,而這個(gè)函數(shù)的作用就是給其他函數(shù)添加一些其他功能作為裝飾。
- 裝飾器不能去修改原函數(shù)的源代碼,只能是增加修飾
- 裝飾器不能去修改原函數(shù)的調(diào)用方式 ,比如函數(shù)fun(),調(diào)用方式就是fun(),不能改變。
怎么實(shí)現(xiàn)裝飾器
裝飾器如果往簡(jiǎn)單的說就是以前學(xué)習(xí)的幾個(gè)知識(shí)點(diǎn)結(jié)合組合在一起。
裝飾器= 高階函數(shù)+嵌套函數(shù)
高階函數(shù)
-
將函數(shù)名作為參數(shù)傳遞給另一個(gè)函數(shù)
在python里,函數(shù)也是可以像變量那樣直接賦值給另一個(gè)變量的,這樣子就可以把函數(shù)名作為實(shí)參傳給另一個(gè)函數(shù)了。(依據(jù)這個(gè)就可以保證裝飾器特點(diǎn)之一:不修改原函數(shù)的源代碼,只添加功能)
def func(): print("Hello I am func") def main(var): print("Before var()") var() print("End var()") #把func作為參數(shù)傳給main main(func) # 這樣子就給func()函數(shù)執(zhí)行前和執(zhí)行后都添加了一句打印,是不是沒有修改了源碼,只添加了功能? -
返回值包含函數(shù)名
函數(shù)名字可以作為參數(shù)傳遞,那么自然函數(shù)名也可以作為返回值了。
在上面的例子中,雖然沒有修改源碼就在函數(shù)執(zhí)行前后添加了一句打印,但是這時(shí)候函數(shù)的調(diào)用方式變?yōu)榱?strong>main(func) ,而原函數(shù)的調(diào)用方式應(yīng)該是func() ,這就與裝飾器的另一個(gè)特點(diǎn):不能修改原函數(shù)的調(diào)用方式?jīng)_突了,解決方式就是函數(shù)名作為返回值。
def func(): print("Hello I am func") def main(var): print(var) return var print(main(func)) #執(zhí)行結(jié)果:會(huì)先打印func的內(nèi)存地址,然后因?yàn)閞eturn的也是func,所以也會(huì)打印出func的內(nèi)存地址,也就是會(huì)打印兩次func的地址 #也可以把返回值賦值給另一個(gè)變量 test = main(func) test() #等同于func() #為了不改變調(diào)用方式,我可以這么做: func = main(func) func() #這樣子就跟直接調(diào)用func(),看起來一樣了,調(diào)用方式?jīng)]有改變
嵌套函數(shù)
嵌套函數(shù),就是之前講過的內(nèi)部函數(shù),在函數(shù)內(nèi)部定義另一個(gè)函數(shù),這里就當(dāng)做溫習(xí)下,這里注意內(nèi)部函數(shù)調(diào)用和沒調(diào)用的差別,整體理解不算難。
# 內(nèi)部函數(shù)只定義,沒有調(diào)用
def out():
print("I am out")
def _in():
print("I am _in")
# 只調(diào)用out()
out() # 只會(huì)執(zhí)行out(),內(nèi)部函數(shù)_in()不會(huì)執(zhí)行,因?yàn)閮?nèi)部只定義并沒有調(diào)用
# 內(nèi)部函數(shù)即定義又調(diào)用
def out1():
print("I am out1")
def _in1():
print("I am _in1")
return _in1
func = out1();
func()
裝飾器
1.有了高階函數(shù)和嵌套函數(shù)的基礎(chǔ),對(duì)于實(shí)現(xiàn)一個(gè)裝飾器就顯得比較好辦了,從上面例子看好像高階函數(shù)就能實(shí)現(xiàn)了裝飾器的兩個(gè)特點(diǎn),但其調(diào)用方式太繞,而高階函數(shù)又恰好能解決這個(gè),兩者結(jié)合就能實(shí)現(xiàn)裝飾器了。
# 定義一個(gè)裝飾器
def dec(func):
# 內(nèi)部定義函數(shù)
def _in():
print("Before func")
func()
print("End func")
return _in
# 定義一個(gè)原函數(shù)
def test():
print("I am test()")
test() # 最初的調(diào)用
test = dec(test) # 把返回值賦值給test,顯得調(diào)用方式并沒有變化
test()
上面例子dec就是一個(gè)裝飾器,但是我們?cè)谡{(diào)用的時(shí)候總是需要把返回值賦值給原函數(shù)名的變量,這樣略顯麻煩,在python里,有一個(gè)裝飾器的語法糖,就是在定義需要裝飾的函數(shù)前面用@ + 裝飾器名字,比如上例子中可以改為:
# 定義一個(gè)裝飾器
def dec(func):
# 內(nèi)部定義函數(shù)
def _in():
print("Before func")
func()
print("End func")
return _in
# 定義一個(gè)原函數(shù)
@dec
def test():
print("I am test()")
test()
2.有沒有發(fā)現(xiàn)上面的例子中,原函數(shù)都是沒有帶參數(shù)的?下面說說帶參數(shù)的原函數(shù),裝飾器怎么寫。
說下思路:在裝飾器里,調(diào)用原函數(shù)的地方是在內(nèi)部函數(shù)里,return返回的也是內(nèi)部函數(shù)的地址,那么執(zhí)行的時(shí)候也是執(zhí)行了內(nèi)部函數(shù),所以如果原函數(shù)有參數(shù),那么內(nèi)部函數(shù)也應(yīng)該要相應(yīng)跟著參數(shù)才可以,否則一定會(huì)出錯(cuò),參數(shù)不匹配了。
# 函數(shù)參數(shù)個(gè)數(shù)是有限的
def dec(func):
def _in(arg):
print("Before func")
func(arg)
print("End func")
return _in
@dec
def test(a):
print("the arg is %d" %a)
test(4)
# 參數(shù)個(gè)數(shù)不限 這里就需要用到 *args **kwargs 了
def dec1(func1):
def _in1(*args,**kwargs):
print("Before func1")
func1(*args,**kwargs)
print("End func1")
return _in1
@dec1
def test1(name,arg):
print("The name is %s,arg is %d" %(name,arg))
test1("Test",12)
注意,如果一個(gè)原函數(shù)中,有多個(gè)裝飾器,那么裝飾器的執(zhí)行順序是怎樣的呢?
答案是從靠近函數(shù)頭的開始執(zhí)行,依次向上,比如:
@dec2
@dec1
def func():
? pass
執(zhí)行順序是從里到外,依次向上,等效于func = dec2(dec1(func))
3.原函數(shù)可以有參數(shù),有沒有想過裝飾器也可以有參數(shù)的?很簡(jiǎn)單嘛,本文開頭就說了裝飾器本質(zhì)就是函數(shù),那它肯定也可以有函數(shù)啦!裝飾器帶參數(shù)的做法,就是再多嵌套一層函數(shù)
# 不帶參數(shù)的裝飾器
def dec(func):
def _in(*args,**kwargs):
print("I am _in")
func(*args,**kwargs)
return _in
# 帶參數(shù)的裝飾器
def dec1(flag = 0):
def real_dec(func):
def _in(*args,**kwargs):
if flag == 0:
print("Run func")
func(*args,**kwargs)
else:
print("No Run func")
return _in
return real_dec
@dec
def test():
print("I am test")
@dec1(1)
def test1():
print("I am test1")
test()
print("-------------")
test1()
注意:帶參數(shù)的裝飾器,在裝飾函數(shù)的時(shí)候要帶上(),比如上面的@dec1()
另外,除了函數(shù)帶參數(shù),函數(shù)還有返回值,在裝飾器里也是一樣的,原函數(shù)需要返回值,那么在內(nèi)部函數(shù)里就需要return。
4.原函數(shù)帶有return的情況
def dec(func):
def _in(*args,**kwargs):
print("I am _in")
ret = func(*args,**kwargs)
print("ret = %d" %(ret))
return ret
return _in
@dec
def test(a,b): #兩個(gè)數(shù)相加
return a+b
test(3,4)