Hi, 大家好。我是茶桁整份。
在最近幾期的課程中待错,相信小伙伴們都頻繁的看到一個詞:「裝飾器」籽孙, 那到底什么是裝飾器,又有什么作用呢火俄?我們這節(jié)課犯建,就來好好的來了解一下。
裝飾器定義
裝飾器就是在不改變原有函數(shù)代碼瓜客,且保持原函數(shù)調(diào)用方法不變的情況下适瓦,給原函數(shù)增加新的功能(或者給類增加屬性和方法)。
核心思想:用一個函數(shù)(或者類)去裝飾一個舊函數(shù)(或者類)谱仪,造出一個新函數(shù)(或者新類)玻熙。
應(yīng)用場景:引入日子,函數(shù)執(zhí)行時間的統(tǒng)計疯攒,執(zhí)行函數(shù)錢的準(zhǔn)備工作揭芍,執(zhí)行函數(shù)后的處理工作,權(quán)限校驗卸例,緩存等。
語法規(guī)則:在原有的函數(shù)上加上@
符肌毅,裝飾器會把下面的函數(shù)當(dāng)作參數(shù)傳遞到裝飾器中筷转,@
符又被稱為「語法糖」。
裝飾器原型
裝飾器其實就是利用閉包悬而,把函數(shù)當(dāng)作參數(shù)傳遞呜舒,并在在函數(shù)內(nèi)去調(diào)用傳遞進(jìn)來的函數(shù),并返回一個函數(shù)笨奠。
來袭蝗,我們還是用代碼來學(xué)習(xí),先讓我們定義一個普通函數(shù):
# 定義一個普通函數(shù)
def old():
print('我是一個普通的函數(shù)')
old() # 作為普通函數(shù)直接調(diào)用
---
我是一個普通的函數(shù)
現(xiàn)在讓我們定義一個嵌套函數(shù)般婆,分為外函數(shù)和內(nèi)函數(shù)兩部分:
# 定義外函數(shù)到腥,接受一個函數(shù)作為參數(shù)
def outer(f):
# 定義內(nèi)函數(shù)蔚袍, 并且在內(nèi)函數(shù)中調(diào)用了外函數(shù)的參數(shù)
def inner():
print('我是外函數(shù)中的內(nèi)函數(shù)1')
f()
print('我是外函數(shù)中的內(nèi)函數(shù)2')
return inner
這里面我們在內(nèi)函數(shù)中打印了兩句話乡范,在兩句話中間執(zhí)行了一次外函數(shù)的參數(shù)(傳遞進(jìn)來一個函數(shù))。最后講內(nèi)函數(shù)作為參數(shù)返回啤咽。
然后我們講剛才的普通函數(shù)old
作為參數(shù)傳進(jìn)去晋辆,然后再用外函數(shù)返回的inner
內(nèi)函數(shù)重新賦值普通函數(shù)old
,最后讓我們再執(zhí)行一遍old
函數(shù), 這個時候宇整,因為old
被重新賦值瓶佳,其實等同于調(diào)用了inner
函數(shù)。來鳞青,我們看看結(jié)果:
old = outer(old) # outer返回了inner函數(shù)霸饲,賦值給了old
old()
---
我是外函數(shù)中的內(nèi)函數(shù)1
我是一個普通的函數(shù)
我是外函數(shù)中的內(nèi)函數(shù)2
是不是稍顯繁雜为朋?那讓我們換個思路,現(xiàn)在我們已經(jīng)先定義好了outer
he inter
贴彼,兩者關(guān)系不變潜腻。還是之前那些代碼,那么我們?nèi)绾卫醚b飾器來進(jìn)行調(diào)用呢器仗?
# 裝飾器用法
@outer # 此處將outer作為了裝飾器
def old():
print('我是一個普通的函數(shù)')
我們在定義old
函數(shù)的時候融涣,直接加上一個@
語法糖,就將outer
作為了裝飾器精钮。這個裝飾器的作用就等同于old = outer(old)
威鹿。那讓我們打印看看結(jié)果:
old()
---
我是外函數(shù)中的內(nèi)函數(shù)1
我是一個普通的函數(shù)
我是外函數(shù)中的內(nèi)函數(shù)2
那我們現(xiàn)在完成了裝飾器的用法,按照定義轨香,我們在不改變old
函數(shù)的代碼忽你,且保持了old
函數(shù)調(diào)用方法不變的情況下,增加了新的方法outer
臂容。
old
函數(shù)經(jīng)過outer
裝飾器進(jìn)行了裝飾科雳,代碼和調(diào)用方法不變,但是函數(shù)的功能發(fā)生了改變脓杉。
你是不是這個時候又有疑問了糟秘,那裝飾器要用在什么地方呢?讓我們來實現(xiàn)一個應(yīng)用:
裝飾器應(yīng)用:統(tǒng)計函數(shù)的執(zhí)行時間
在正式寫代碼之前球散,我還是習(xí)慣帶著大家先思考一遍尿赚。我們需要統(tǒng)計函數(shù)的執(zhí)行時間,那我們需要什么關(guān)鍵點蕉堰?
- 開始時間凌净,
- 結(jié)束時間
- 開始時間和結(jié)束時間之間,就是程序在運行的過程屋讶。
好的冰寻,讓我們來開始寫代碼,先來一段簡單的需要運行的程序丑婿,為了能順利統(tǒng)計時間性雄,我們給它設(shè)定兩個東西,一個循環(huán)羹奉,一個停止運行時長秒旋。這樣,我們不會因為程序運行過快而看不到結(jié)果:
import time
# 定義一個普通函數(shù)
def func():
for i in range(5):
print(i, end=" ")
time.sleep(1)
函數(shù)寫完之后诀拭,讓我們來執(zhí)行一下看看:
func()
---
0 1 2 3 4
沒問題迁筛,確實是一秒打印一次。
現(xiàn)在耕挨,再來讓我們完成要稱為裝飾器的統(tǒng)計函數(shù):
# 定義一個統(tǒng)計函數(shù)執(zhí)行時間的裝飾器
def runtime(f):
def inner():
start = time.perf_counter()
f()
end = time.perf_counter() - start
print(f'\n函數(shù)的調(diào)用執(zhí)行時間為:{end}')
return inner
在函數(shù)inner
中细卧,我們最終是打印了最終的時間end
尉桩, 然后將整個inner
函數(shù)返回。
那么贪庙,讓我們來嘗試執(zhí)行一下看看吧:
func = runtime(func)
func()
---
0 1 2 3 4
函數(shù)的調(diào)用執(zhí)行時間為:5.017943917075172
這樣蜘犁,我們就得到了func
這個函數(shù)最后執(zhí)行的時間,那現(xiàn)在的問題是止邮,統(tǒng)計時間的函數(shù)是一個通用函數(shù)这橙,我們很多函數(shù)中都需要用到它進(jìn)行統(tǒng)計。但是我們總不能所有的函數(shù)都要用這種方法重新賦值之后再調(diào)用吧导披?
那我們就用裝飾器來解決就好了:
# 定義一個普通函數(shù)
@runtime
def func():
for i in range(5):
print(i, end=" ")
time.sleep(1)
func()
---
0 1 2 3 4
函數(shù)的調(diào)用執(zhí)行時間為:5.017888417001814
當(dāng)然屈扎,最終這種函數(shù)的調(diào)用執(zhí)行時間并不會像現(xiàn)在這樣打印到前臺,而是會寫進(jìn)log
變?yōu)槿罩敬鎯ζ饋砹秘埃阌谥蠓治鍪褂谩?/p>
裝飾器嵌套語法
在這一段代碼中鹰晨,我們來約個妹子,完成一場約會止毕。從哪開始呢模蜡?就從找妹子要微信開始吧:
def begin(f):
def begin_inner():
print('找妹子要微信,成功...')
f()
print('送妹子回家...')
return begin_inner
@begin
def love():
print('跟妹子暢談人生和理想...')
love()
---
找妹子要微信扁凛,成功...
跟妹子暢談人生和理想...
送妹子回家...
那這樣哩牍,我們實現(xiàn)了一段最普通裝飾器的定義。現(xiàn)在讓我們在下面再定義一個裝飾器函數(shù)令漂,為什么呢?因為我漸漸不滿足于只談理想和人生了丸边,要有點實際的行動了,順便叠必,我們寫了一個列表,把和妹子要做的事情都列了個順序妹窖,再來看看:
def begin(f):
def begin_inner():
print('找妹子要微信纬朝,成功... 1')
f()
print('送妹子回家... 5')
return begin_inner
def evolve(f):
def evolve_inner():
print('和妹子一起吃了個大餐.. 2')
f()
print('和妹子看了一場夜場電影... 4')
return evolve_inner
@evolve
@begin
def love():
print('跟妹子暢談人生和理想... 3')
love()
---
和妹子一起吃了個大餐.. 2
找妹子要微信,成功... 1
跟妹子暢談人生和理想... 3
送妹子回家... 5
和妹子看了一場夜場電影... 4
這個...順序似乎不太對啊骄呼。讓我們改變一下裝飾器的順序試試:
@begin
@evolve
def love():
print('跟妹子暢談人生和理想... 3')
love()
---
找妹子要微信共苛,成功... 1
和妹子一起吃了個大餐.. 2
跟妹子暢談人生和理想... 3
和妹子看了一場夜場電影... 4
送妹子回家... 5
這回沒錯了,我們在最開始要妹子微信和最后送妹子回家中間蜓萄,又進(jìn)行了點什么隅茎。也算是有些進(jìn)展了。那么嫉沽,我們怎么去理解這個程序運行順序呢辟犀?
- 先使用離得最近的
begin
裝飾器,裝飾love
函數(shù)绸硕,返回了一個begin_inner
函數(shù) - 在使用上面的
evolve
, 裝飾了上一次返回的begin_inner
函數(shù)堂竟,又返回了一個evolve_inner
函數(shù)魂毁。
在調(diào)用完成之后,就是需要順序執(zhí)行了出嘹,其執(zhí)行的嵌套關(guān)系和順序如下:
那這席楚,就是我們嵌套裝飾器的用法。當(dāng)然税稼,這種嵌套裝飾器的用法并不常見烦秩,可是一旦我們遇到了,要理解他的運行機(jī)制和順序娶聘,避免不必要的麻煩闻镶。
裝飾帶有參數(shù)的函數(shù)
上一個部分,我們做了一個約會妹子的函數(shù)丸升,并且使用裝飾器進(jìn)行了裝飾铆农。使的我們成功的按照進(jìn)度依次執(zhí)行了自己的計劃。
但是問題來了狡耻,我們到目前為止約會了那么多妹子墩剖,都不知道誰是誰(海王體質(zhì)),這可怎么辦夷狰。這次岭皂,我們吸取教訓(xùn),先要名字沼头,既然之前的流程很成功爷绘,我們直接拿來使用就行了。但是繁雜的步驟我們都去掉进倍,直奔主題:
# 帶有參數(shù)的函數(shù)
def love(name):
print(f'跟{name}妹子在___暢談人生...')
一個簡單的函數(shù)土至,并且是一個填空題,你愿意待誰去哪里暢談人生猾昆,隨便陶因。比如我,找「露思」去垂蜗,去了哪里楷扬,恕不奉告了:
love('露思')
---
跟露思妹子在___暢談人生...
可是即便如此,該有的流程還是不能丟贴见,總不能憑空變出個妹子吧烘苹,還是得把必要的流程加上,原本定義的裝飾器函數(shù)似乎不能使用了:
# 定義裝飾器
def outer(f):
def inner():
print(f'找到妹子片部,成功的拿到了微信...')
f()
print(f'約妹子去看一場午夜電影...')
return inner
@outer
def love(name):
print(f'跟{name}妹子在___暢談人生...')
流程上現(xiàn)在是沒問題了螟加,可是我們執(zhí)行一下發(fā)現(xiàn),報錯了。
love('露思')
---
TypeError: outer..inner() takes 0 positional arguments but 1 was given
進(jìn)行不下去了吧捆探?那沒辦法然爆,誰叫你之前和之后都把人家名字忘了呢,海王也得有點職業(yè)道德才行黍图。既然我們在執(zhí)行的時候有參數(shù)曾雕,那你整個過程中都得帶上才行。不能玩著玩著忘記人家名字助被,對吧剖张。讓我們改進(jìn)一下,既然我們已經(jīng)知道揩环,在使用裝飾器裝飾過后的love()
執(zhí)行實際上是執(zhí)行裝飾器inner
搔弄, 那我們嘗試給inner
加上參數(shù)進(jìn)行傳遞,還有很重要的丰滑,我們之前和之后顾犹,得把妹子名字記清楚才行,所以執(zhí)行的時候也記得加上:
# 定義裝飾器
def outer(f):
def inner(var):
print(f'找到{var}妹子褒墨,成功的拿到了微信...')
f(var)
print(f'約{var}妹子去看一場午夜電影...')
return inner
# 帶有參數(shù)的函數(shù)
@outer
def love(name):
print(f'跟{name}妹子在___暢談人生...')
love('露思')
---
找到露思妹子炫刷,成功的拿到了微信...
跟露思妹子在___暢談人生...
約露思妹子去看一場午夜電影...
嗯,這樣一場和「露思」妹子之間完美的從認(rèn)識到約會流程就完成了郁妈。我們總結(jié)一下:
如果裝飾器帶有參數(shù)的函數(shù)浑玛,需要在內(nèi)函數(shù)中定義形參,并傳遞給調(diào)用的函數(shù)噩咪。因為調(diào)用原函數(shù)等于調(diào)用內(nèi)函數(shù)顾彰。
裝飾多參數(shù)的函數(shù)
上一個流程跑完之后呢,我覺得還是不太妥當(dāng)胃碾。主要是其中有兩個選擇題拘央,一個是誰,一個是去哪里书在。對吧。再說了拆又,我也得跟妹子自我介紹一下儒旬,加強(qiáng)一點印象。
好帖族,我們這次再多設(shè)置幾個參數(shù)栈源,讓整個約會過程更完善一些,那我們從最初就要規(guī)劃一下竖般,需要的參數(shù)包括:我甚垦,妹子,地點,行為
等等艰亮。還蠻多的闭翩。
# 裝飾帶有多參數(shù)的函數(shù)
def outer(f):
def inner(man,name,*args,**kwargs):
print(f'{man}要到了{(lán)name}妹子的微信...')
f(man, name, *args, **kwargs)
print('天色漸晚...')
return inner
# 定義多參數(shù)的函數(shù)
@outer
def love(man, name, *args, **kwargs):
print(f'{man}跟{name}暢談人生...')
print(f'帶{name}妹子去吃了很多美食:', args)
print(f'和{name}妹子看了夜場電影:', kwargs)
這樣我們就定義好了,至于*args
以及**kwargs
是什么迄埃,可以翻看之前的教程疗韵。
讓我們現(xiàn)在來執(zhí)行一下:
love('茶桁','露思', '火鍋', '海鮮', '飯后甜點', mov='封神第一部')
---
茶桁要到了露思妹子的微信...
茶桁跟露思暢談人生...
帶著露思妹子去吃了很多美食: ('火鍋', '海鮮', '飯后甜點')
和露思妹子看了夜場電影: {'mov': '封神第一部'}
天色漸晚...
這樣,多道選擇題就被我們一一的化解了侄非。相信「露思」妹子對我們的整體安排也是相當(dāng)?shù)臐M意了厅缺。
帶有參數(shù)的裝飾器
在我們平時使用Python各種第三方庫的時候押框,不可避免的會遇到帶有參數(shù)的裝飾器。比如說,Django
框架中的@login_required(redirect_field_name="my_redirect_field")
烹骨。
這種帶有參數(shù)的裝飾器是干嘛的呢?還是拿我們之前的海王約會流程來舉例纲缓。之前的流程是都沒有什么問題牲蜀,可是有沒有發(fā)現(xiàn),所有事情都是我們自己做主了眯搭,似乎妹子一直都沒有反對過窥翩,也沒有說自己想要什么。這是不是不太符合現(xiàn)實鳞仙?
沒錯寇蚊,我們也要給妹子裝上一個會思考的大腦,也要學(xué)會做判斷棍好,好仗岸,讓我們來實現(xiàn)一下:
既然我們這節(jié)是學(xué)習(xí)帶有參數(shù)的裝飾器,那么必然裝飾器上是帶參數(shù)的借笙。呃扒怖,不要認(rèn)為這句話是廢話,我們看代碼:
@put(var)
def love():
print('暢談人生...')
就像這樣业稼,我們給裝飾器加上了參數(shù)盗痒。
那么現(xiàn)在問題就來了,我們來看看我們之前寫的裝飾器函數(shù):
def outer(f):
def inner():
pass
return inner
發(fā)現(xiàn)有什么問題了么低散?雖然我們的外層函數(shù)outer
是有形參的俯邓,但是我們之前的過程中了解到,這個形參f
是為了接收當(dāng)前執(zhí)行函數(shù)的熔号。那還有什么其他地方接收非函數(shù)的普通參數(shù)嘛稽鞭?
既然outer
中很重要的作用,除了接收函數(shù)在內(nèi)函數(shù)內(nèi)執(zhí)行引镊,還有一個就是返回inner
內(nèi)函數(shù)朦蕴,那么我們在不改變outer
的基礎(chǔ)之上篮条,再加一個接收普通參數(shù)的函數(shù)不就行了。
def put(var):
def outer(f):
def inner1():
print('妹子給了你微信')
def inner2():
print('妹子給了你她閨蜜的微信')
def inner3():
print('妹子送了你一句感人肺腑的話:滾...')
# 裝飾器殼的參數(shù)吩抓,可以用于在函數(shù)內(nèi)去做流程控制涉茧。
if var == 1:
return inner1
elif var == 2:
return inner2
else:
return inner3
return outer
@put(2)
def love():
print('暢談人生...')
定義完成之后,我們來執(zhí)行一下看看:
love()
---
妹子給了你她閨蜜的微信
家人們誰懂啊琴拧,妹子真把我當(dāng)海王了嘛降瞳?最后,我還是老老實實的接受了妹子的好意蚓胸。
從整段代碼中我們可以看出來挣饥,如果裝飾器中有參數(shù),需要有一個外殼函數(shù)來接收參數(shù)沛膳,傳參之后就會進(jìn)入到下一層函數(shù)中扔枫,并且傳遞當(dāng)前對象。再然后才會再進(jìn)入下一層中去锹安。當(dāng)然短荐,我們在這里,利用傳遞的參數(shù)寫了一段if
判斷叹哭,用于確定妹子的決定是什么忍宋。然后我們就返回哪個決定的函數(shù)。最后別忘記风罩,在外殼函數(shù)中糠排,我們還需要講outer
函數(shù)返回出去。此時雖然love
函數(shù)是outer
函數(shù)超升,但是在之前入宦,put
裝飾器已經(jīng)將參數(shù)傳遞給了外殼函數(shù)put(var)
。在裝飾器函數(shù)的爭端代碼中室琢,我們都沒有再執(zhí)行過傳進(jìn)來的參數(shù)乾闰,也就是函數(shù)love()
, 所以此段代碼中love
函數(shù)中的打印方法并未執(zhí)行盈滴。
其執(zhí)行步驟為:
put(var) => outer() => outer(love) => inner2()
用類裝飾器裝飾函數(shù)
之前我們所有的代碼中涯肩,裝飾器一直使用的都是函數(shù)裝飾器。那我們能否用類來當(dāng)裝飾器裝飾函數(shù)呢巢钓?
試試不就知道了病苗。
# 類裝飾器裝飾函數(shù)
class Outer():
def __call__(self, func):
self.func = func
return self.inner
def inner(self, who):
print('拿到妹子的微信...')
self.func(who)
print('看一場午夜電影...')
@Outer()
def love(who):
print(f'{who}和妹子談理想與人生...')
寫完了,這下我們省略了那么多雜七雜八的流程竿报,因為我發(fā)現(xiàn),那么多流程下來继谚,最終感動的人只有自己烈菌。妹子愿意阵幸,怎么都愿意,不愿意的芽世,無論做多少都不愿意挚赊。
來,讓我們跑一下程序試試:
love('茶桁')
---
拿到妹子的微信...
茶桁和妹子談理想與人生...
看一場午夜電影...
沒問題济瓢,正確的執(zhí)行荠割。那這個時候的love
函數(shù)到底是什么呢?我們來打印出來看看:
print(love)
---
<bound method Outer.inner of <__main__.Outer object at 0x106646c80>>
可以看到旺矾,此時的love
函數(shù)就是Outer
類中的inner
函數(shù)蔑鹦。那我們怎么理解整個代碼呢?
我們在love
函數(shù)上使用了裝飾器Outer
, 那么這個時候Outer
就會實例化出來一個對象obj
箕宙,然后這個@obj
就等同于obj(love)
嚎朽。
然后我們實例化對象進(jìn)入Outer()
內(nèi)部,進(jìn)入之后遇到了魔術(shù)方法__call__
, 它會把該類的對象當(dāng)作函數(shù)調(diào)用時自動觸發(fā)柬帕。也就是obj()
觸發(fā)哟忍。
還記得類的實例化么?會傳入一個參數(shù)陷寝,也就是實例化對象本身:obj
锅很。并且,第二個參數(shù)func
用來接收了傳遞進(jìn)來的函數(shù)love
凤跑, 設(shè)置了self.func = func
, 把傳進(jìn)來的函數(shù)作為對象的成員方法爆安。最后返回了一個函數(shù)inner
, 這個返回的函數(shù)是類中定義好的饶火,于是作為實例化對象也將這個成員方法繼承了下來鹏控,所以self.inner
可以直接被返回出去。
這個定義好的inner
接收了兩個形參肤寝,一個是實例化對象本身当辐,一個就是傳遞進(jìn)來的函數(shù)love
的參數(shù)who
。然后鲤看,中間執(zhí)行了一下魔術(shù)方法__call__
內(nèi)定義好的self.func(who)
缘揪,實際上也就是obj.love(who)
。
看著迷糊义桂?這樣找筝,我寫一個注釋過的完整版本。
# 類裝飾器裝飾函數(shù)
class Outer():
# 魔術(shù)方法:當(dāng)把該類的對象當(dāng)作函數(shù)調(diào)用時慷吊,自動觸發(fā)obj()
def __call__(self, func):
# 把傳進(jìn)來的函數(shù)作為對象的成員方法
self.func = func
# 返回一個函數(shù)
return self.inner
# 在定義的需要返回的新方法中袖裕,去進(jìn)行裝飾和處理
def inner(self, who):
print('拿到妹子的微信...')
self.func(who)
print('看一場午夜電影...')
'''
Outer() 實例化對象 => obj
@obj 就等于 obj(love)
進(jìn)入類后 => __call__(love)
接收返回參數(shù)`inner()`
'''
@Outer()
def love(who):
print(f'{who}和妹子談理想與人生...')
# inner('茶桁')
love('茶桁')
# 此時的love就是屬于`Outer`類這個對象中的inner方法
print(love)
不知道這段注釋代碼加上剛才的解說,大家能否看懂溉瓶?有沒有發(fā)現(xiàn)急鳄,用類做裝飾器比起函數(shù)裝飾器反而更清晰一點谤民?不需要寫那么多外層函數(shù)和內(nèi)層函數(shù)。
讓我們繼續(xù)...
類方法裝飾函數(shù)
剛才我們將整個類都用作了一個裝飾器疾宏,那我們思考一下张足,是不是我們還可以用類中的方法來做裝飾器呢?
說干就干坎藐,直接上代碼測試:
# 用類方法裝飾函數(shù)
class Outer():
def newinner(f):
# 把傳遞進(jìn)來的函數(shù)定義為類方法
Outer.func = f
# 同時返回一個新的類方法
return Outer.inner
def inner():
print('拿到妹子微信...')
Outer.func()
print('看一場午夜電影...')
'''
Outer.newinner(love)
接收返回參數(shù):Outer.inner
'''
@Outer.newinner
def love():
print('和妹子談?wù)勅松群炔?..')
# love() 等于 Outer.inner()
love()
---
拿到妹子微信...
和妹子談?wù)勅松群炔?..
看一場午夜電影...
在經(jīng)歷了幾場約會之后为牍,我們的耐心也漸漸沒了。連是誰都不管岩馍,也沒耐心去談理想了碉咆。喝點茶聊聊天就直奔主題了都是。
到目前為止以上所有形式的裝飾器兼雄,包括「函數(shù)裝飾器」吟逝、「類裝飾器」、「類方法裝飾器」赦肋,都有一個共同特點:都是在給函數(shù)去進(jìn)行裝飾块攒,增加功能。
那我們這個時候就不滿足了佃乘,既然能裝飾函數(shù)囱井,那是否也能裝飾類呢?
用裝飾器裝飾類
還真有一種裝飾器是專門裝飾類的趣避,也就是在類的定義的前面使用@
裝飾器這種語法庞呕。和裝飾函數(shù)并無什么區(qū)別,只是放在了類前面而已:
@裝飾器
class Demo():
pass
裝飾器給函數(shù)進(jìn)行裝飾程帕,目的是不改變函數(shù)調(diào)用和代碼的情況下給原函數(shù)增加新的功能住练。
裝飾器給類進(jìn)行裝飾,目的是不改變類的定義和調(diào)用的情況下給類增加新的成員(屬性或者方法)愁拭。
來讲逛,讓我們具體的看看:
函數(shù)裝飾器裝飾類
# 定義函數(shù),接收一個類岭埠。返回修改后的類
def expand(cls):
def func2():
print('我是在裝飾器中追加的新方法盏混,func2')
cls.func2 = func2 # 把剛才定義的方法賦值給 類
cls.name = '我是在裝飾器中追加的新屬性 name'
# 返回時,把追加類新成員的類返回去
return cls
@expand # expand(Demo) ==> cls ==> Demo
class Demo():
def func():
print('我是Demo類中定義的func方法')
Demo.func() # 此時在調(diào)用的Demo類是通過裝飾器惜论,更新過的Demo類
Demo.func2()
print(Demo.name)
---
我是Demo類中定義的func方法
我是在裝飾器中追加的新方法许赃,func2
我是在裝飾器中追加的新屬性 name
這樣,我們在原來的Demo
這個類中馆类,使用裝飾器增加了一個成員方法func2
混聊,并且增加了一個成員屬性name
, 并最終返回到原始類中乾巧。從而擴(kuò)展了這個原始類Demo
中的方法和屬性句喜。
類裝飾器裝飾類
# 使用類裝飾器裝飾類
class expand():
def __call__(self, cls):
# 把接收的類僵闯,賦值給當(dāng)前對象,作為一個屬性
self.cls = cls
# 返回一個函數(shù)
return self.newfunc
def newfunc(self):
self.cls.name = '我是在類裝飾器中追加的新屬性 name'
self.cls.func2 = self.func2
# 返回傳遞進(jìn)來的類的實例化結(jié)果藤滥,obj
return self.cls()
def func2(self):
print('我是在類裝飾器中追加的新方法 func2')
'''
expand() ==> obj ==> @obj(Demo) ==> __call__(Demo) ==> newfunc
'''
@expand()
class Demo():
def func(self):
print('我是Demo類中定義的func方法')
obj = Demo() # Demo() ==> newfunc() ==> obj
obj.func()
obj.func2()
print(obj.name)
在之前那么多案例過后,相信大家這一段代碼應(yīng)該能看的出來吧社裆。
那我這個地方要處一個思考題了拙绊,請問:此時的 obj
這個對象,是哪個類的對象泳秀。Demo
還是expand
?
print(obj)
---
???
這個問題的答案标沪,我放在源碼中了,大家要記得思考之后再去看答案嗜傅。
那么金句,本節(jié)課的內(nèi)容到這里也就結(jié)束了。
課程進(jìn)行到這里吕嘀,我們Python本身的所有內(nèi)容就已經(jīng)介紹完了违寞。下節(jié)課開始,我們就要考試講第三方庫偶房。
好趁曼,下課。