24. 裝飾器語法與應(yīng)用

cover.png

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)先定義好了outerhe 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)鍵點蕉堰?

  1. 開始時間凌净,
  2. 結(jié)束時間
  3. 開始時間和結(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
5sec_func.gif

沒問題迁筛,確實是一秒打印一次。

現(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)展了。那么嫉沽,我們怎么去理解這個程序運行順序呢辟犀?

  1. 先使用離得最近的begin裝飾器,裝飾love函數(shù)绸硕,返回了一個begin_inner函數(shù)
  2. 在使用上面的evolve, 裝飾了上一次返回的begin_inner函數(shù)堂竟,又返回了一個evolve_inner函數(shù)魂毁。

在調(diào)用完成之后,就是需要順序執(zhí)行了出嘹,其執(zhí)行的嵌套關(guān)系和順序如下:

love_func.png

那這席楚,就是我們嵌套裝飾器的用法。當(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")烹骨。

image-20230819183310594.png

這種帶有參數(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é)課開始,我們就要考試講第三方庫偶房。

好趁曼,下課。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棕洋,一起剝皮案震驚了整個濱河市挡闰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掰盘,老刑警劉巖摄悯,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異愧捕,居然都是意外死亡奢驯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門晃财,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叨橱,“玉大人,你說我怎么就攤上這事断盛÷尴矗” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵钢猛,是天一觀的道長伙菜。 經(jīng)常有香客問我,道長命迈,這世上最難降的妖魔是什么贩绕? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任火的,我火速辦了婚禮,結(jié)果婚禮上淑倾,老公的妹妹穿的比我還像新娘馏鹤。我一直安慰自己,他們只是感情好娇哆,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布湃累。 她就那樣靜靜地躺著,像睡著了一般碍讨。 火紅的嫁衣襯著肌膚如雪治力。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天勃黍,我揣著相機(jī)與錄音宵统,去河邊找鬼。 笑死覆获,一個胖子當(dāng)著我的面吹牛马澈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弄息,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼箭券,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了疑枯?” 一聲冷哼從身側(cè)響起辩块,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荆永,沒想到半個月后废亭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡具钥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年豆村,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂删。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡掌动,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宁玫,到底是詐尸還是另有隱情粗恢,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布欧瘪,位于F島的核電站眷射,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妖碉,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一涌庭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欧宜,春花似錦坐榆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚀狰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間职员,已是汗流浹背麻蹋。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留焊切,地道東北人扮授。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像专肪,于是被迫代替她去往敵國和親刹勃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容

  • ### 裝飾器定義 **在不改變原有函數(shù)代碼嚎尤,且保持原函數(shù)調(diào)用方法不變的情況下荔仁,給原函數(shù)增加新的功能(或者給類增加...
    lmonkey_01閱讀 201評論 0 0
  • 在python編程中,我們經(jīng)逞克溃看到下面的函數(shù)用法: with open("test.txt", "w") as f...
    hugoren閱讀 816評論 0 0
  • 裝飾器本質(zhì)上是一個函數(shù)乏梁,該函數(shù)用來處理其他函數(shù),它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能关贵,裝飾器的返...
    胡一巴閱讀 406評論 0 0
  • 裝飾器的強(qiáng)大在于它能夠在不修改原有業(yè)務(wù)邏輯的情況下對代碼進(jìn)行擴(kuò)展遇骑,如進(jìn)行權(quán)限校驗、用戶認(rèn)證揖曾、日志記錄落萎、性能測試、事...
    mysimplebook閱讀 474評論 0 0
  • 多重裝飾器 ?眾所周知炭剪,使用裝飾器裝飾一個函數(shù)時练链,裝飾器會將原函數(shù)當(dāng)做一個參數(shù),傳進(jìn)裝飾器函數(shù)中奴拦,然后返回一個新的...
    西西里加西閱讀 1,662評論 1 1