給一個(gè)函數(shù)添加新功能
1. 裝飾器介紹
def hello(func):
def newfunc():
print("hello", end = " ")
func()
return
return newfunc
@hello
def xiaoli():
print("xiaoli")
return
xiaoli()
#輸出: hello xiaoli
如上面代碼所示担神,裝飾器是一個(gè)函數(shù) 可調(diào)用對(duì)象嘴秸,接收一個(gè)函數(shù)作為參數(shù),并將其替換成一個(gè)函數(shù)馅笙。
2. “裝飾”過程
分析1的代碼躲叼,函數(shù)xiaoli在定義時(shí)只會(huì)輸出"xiaoli",而調(diào)用時(shí)卻額外輸出了"hello "蓄坏,顯然我們的調(diào)用過程事實(shí)上調(diào)用的是hello內(nèi)部定義的newfunc价捧,并且func即為我們實(shí)際定義的xiaoli。
先無視第8行的@hello嘗試手動(dòng)實(shí)現(xiàn)這個(gè)效果涡戳。
def hello(func):
def newfunc():
print("hello", end = " ")
func()
return
return newfunc
def xiaoli():
print("xiaoli")
return
xiaoli()
#輸出:xiaoli
去掉了@hello干旧,正常的調(diào)用了xiaoli,這里和hello沒有什么關(guān)系妹蔽。
hello(xiaoli)
#無輸出
外層函數(shù)hello被調(diào)用,查看返回值發(fā)現(xiàn)返回一個(gè)函數(shù)對(duì)象挠将,根據(jù)hello的定義這里返回了內(nèi)層函數(shù)newfunc胳岂。
嘗試調(diào)用它:
hello(xiaoli)()
#輸出:hello xiaoli
這樣輸出就和1的示例一致了,為了讓調(diào)用過程也保持一致舔稀,加入一行代碼:
xiaoli = hello(xiaoli)
xiaoli()
#輸出:hello xiaoli
這樣就實(shí)現(xiàn)了和1中示例代碼同樣的效果乳丰。對(duì)于那個(gè)@hello我們可以理解為它就是在函數(shù)定義結(jié)束后添加了一行xiaoli = hello(xiaoli)。至少目前看來這二者沒有什么區(qū)別内贮。
3. 裝飾帶參數(shù)和返回值的函數(shù)
了解了裝飾器的用法后我們可以試圖讓所有自己定義的函數(shù)都變得懂禮貌产园,只需要在定義時(shí)使用hello裝飾器即可汞斧。如:
@hello
def xiaoyu():
print("xiaoyu")
return
@hello
def xiaoliu():
print("xiaoliu")
return
@hello
def xiaozhou():
print("xiaozhou")
return
充分發(fā)揚(yáng)懶惰精神,我們可以寫成這樣
@hello
def xiaonashei(nashei):
print(xiao + str(nashei))
return
看上去就有毛病什燕,調(diào)用果然出錯(cuò):
In [133]: xiaonashei("nashei")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-133-e12e640621a4> in <module>()
----> 1 xiaonashei("nashei")
TypeError: newfunc() takes 0 positional arguments but 1 was given
顯然是寫hello考慮不周粘勒,newfunc應(yīng)當(dāng)考慮不同參數(shù)情況。
def hello(func):
def newfunc(*args):
print("hello "屎即, end = "")
func(*args)
return
return newfunc
順便發(fā)現(xiàn)第5行內(nèi)部函數(shù)居然沒有返回值庙睡,一并解決。
def hello(func):
def newfunc(*args):
print("hello ", end = "")
result = func(*args)
return result
return newfunc
然后就可以愉快地裝飾xiaonashei了技俐。
@hello
def xiaonashei(nashei):
print(xiao + str(nashei))
return
xiaonashei("penyou")
#輸出:hello xiaopenyou
帶返回值的函數(shù)也能正確獲取返回值乘陪。
@hello
def bbs(nasha):
return abs(nasha)
b = bbs(-1)
#輸出: hello
print(b)
#輸出: 1
4. 參數(shù)化裝飾器
既然裝飾器也是函數(shù),理應(yīng)可以接受其他參數(shù)以實(shí)現(xiàn)不同功能雕擂,嘗試根據(jù)性別打招呼啡邑。
def hello(func,sex):
def newfuncforboy(*args):
print("hello boy ", end = "")
result = func(*args)
return result
def newfuncforgirl(*args):
print("hello girl ", end = "")
result = func(*args)
return result
return newfuncforboy if sex == "m" else newfuncforgirl
運(yùn)行試試
In [130]: @hello("m")
...: def xiaoxiao():
...: print("xiaoxiao")
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-130-fd7cd46b49f4> in <module>()
----> 1 @hello("m")
2 def xiaoxiao():
3 print("xiaoxiao")
4
TypeError: hello() missing 1 required positional argument: 'sex'
為什么會(huì)這樣?回憶2里對(duì)@hello的替換井赌,
@hello("m")
def xiaoxiao():
...
看成
def xiaoxiao():
...
xiaoxiao = hello("m")(xiaoxiao)
也就是說谤逼,xiaoxiao并不會(huì)被當(dāng)做參數(shù)傳入hello,而是被傳入hello("m")族展,事實(shí)上森缠,在那之前代碼就會(huì)因?yàn)閔ello("m")這個(gè)錯(cuò)誤的函數(shù)調(diào)用而中止。
回憶1講過的內(nèi)容仪缸,裝飾器是一個(gè)可調(diào)用對(duì)象贵涵,接收一個(gè)函數(shù)作為參數(shù),并將其替換成一個(gè)函數(shù)。@符號(hào)永遠(yuǎn)認(rèn)為其緊跟著的是一個(gè)裝飾器恰画,在無參的情況下hello是一個(gè)裝飾器宾茂,在帶參數(shù)的情況下hello("m")也應(yīng)當(dāng)是一個(gè)裝飾器。也就是說hello("m")的返回值才應(yīng)當(dāng)是作用于xiaoxiao的裝飾器拴还。
再加入一層函數(shù)嵌套:
def hello(sex):
def inner(func):
def newfuncforboy(*args):
print("hello boy ", end = "")
result = func(*args)
return result
def newfuncforgirl(*args):
print("hello girl ", end = "")
result = func(*args)
return result
return newfuncforboy if sex == "m" else newfuncforgirl
return inner
@hello("m")
def xiaoming():
print("xiaoming")
return
@hello("w")
def xiaohong():
print("xiaohong")
return
xiaoming()
#輸出: hello boy xiaoming
xiaohong()
#輸出: hello girl xiaohong
終于正常實(shí)現(xiàn)功能了跨晴。
事實(shí)上如果使用一開始的寫法,手動(dòng)裝飾片林。
def hello(func,sex):
def newfuncforboy(*args):
print("hello boy ", end = "")
result = func(*args)
return result
def newfuncforgirl(*args):
print("hello girl ", end = "")
result = func(*args)
return result
return newfuncforboy if sex == "m" else newfuncforgirl
def xiaoming():
print("xiaoming")
return
xiaoming = hello(xiaoming,"m")
def xiaohong():
print("xiaohong")
return
xiaohong = hello(xiaohong,"w")
xiaoming()
#輸出: hello boy xiaoming
xiaohong()
#輸出: hello girl xiaohong
好像同樣能夠?qū)崿F(xiàn)這一功能端盆,但是這樣會(huì)導(dǎo)致疊放裝飾器的時(shí)候很麻煩,所以還是建議使用前面的寫法费封。
5. 疊放裝飾器
可以疊放多個(gè)裝飾器焕妙,裝飾器自下而上作用。
def hell0(func):
print("!hell0")
def newfunc():
print("hell0", end = " ")
func()
return
return newfunc
def hell1(func):
print("!hell1")
def newfunc():
print("hell1", end = " ")
func()
return
return newfunc
def hell2(func):
print("!hell2")
def newfunc():
print("hell2", end = " ")
func()
return
return newfunc
@hell2
@hell1
@hell0
def xiaoli():
print("xiaoli")
return
xiaoli()
運(yùn)行上面的代碼弓摘,觀察輸出時(shí)機(jī)和順序即可焚鹊。