深刻理解python裝飾器

我們要完全理解python裝飾器伤柄,不是很容易尺迂,主要?dú)w結(jié)有如下困難:
1. 關(guān)于函數(shù)“變量”(或“變量”函數(shù))的理解
2. 關(guān)于高階函數(shù)的理解
3. 關(guān)于嵌套函數(shù)的理解
放心吸重,我會(huì)用淺顯的例子來(lái)說(shuō)明其中的知識(shí)岭佳。

1. 首先有這樣一個(gè)例子

首先我們用pycharm創(chuàng)建一個(gè)工程,這里我創(chuàng)建的工程名為:py3test,這個(gè)工程名自己取端逼,當(dāng)然你也可以不使用pycharm這款I(lǐng)DE朗兵,直接用notepad++這樣的文本編輯器也是可以的。
然后我們分別在工程目錄先創(chuàng)建三個(gè)文件:

  1. __inti__.py
  2. s.py
  3. test.py
    如下圖所示:
    圖一

接下來(lái)在s.py文件中添加內(nèi)容:

def f1():
    print('f1')


def f2():
    print('f2')


def f100():
    print('f3')

test.py文件中添加內(nèi)容:

import s

s.f1()
s.f2()
s.f100()

__init__.py 文件不用添加內(nèi)容顶滩,有人可能會(huì)想不添加內(nèi)容余掖,那這個(gè)文件有什么用呢?下面我找了幾個(gè)資料可以作為參考:
https://juejin.im/post/5a2cfc4f6fb9a044ff316588
https://segmentfault.com/q/1010000012365228
運(yùn)行test.py文件
運(yùn)行test.py文件后可以看到礁鲁。在test.py中成功的調(diào)用了s.py中的函數(shù)盐欺。

接下來(lái)修改s.py文件

s.py文件中的每個(gè)函數(shù)前都添加

print('裝飾器')

如圖所示

圖二

然后重新運(yùn)行test.py文件,結(jié)果如圖所示:

圖三

接著我們這想這樣的一個(gè)問(wèn)題仅醇,在我們最開(kāi)始學(xué)習(xí)編程的時(shí)候冗美,如果有個(gè)題目要求我們輸出100個(gè)'hello world!',我們自然的想到了,直接print('hello world!')然后寫(xiě)100行,自然也就打印出了100個(gè)hello world!析二,在我們學(xué)習(xí)了循環(huán)之后就知道了直接用一個(gè)for循環(huán)100次就行了粉洼。
同樣的在我們學(xué)習(xí)函數(shù)之前,如果你寫(xiě)的程序中會(huì)多次引用一段相同的代碼甲抖,或者是要多次執(zhí)行相同的功能,然后我們就用函數(shù)把這些需要多次調(diào)用的功能封裝起來(lái)心铃,就變成了函數(shù)准谚,在面對(duì)對(duì)象編程中也叫方法
如果現(xiàn)在有這么一個(gè)需求就是在現(xiàn)有的100個(gè)函數(shù)執(zhí)行前加上print('hello world!')去扣,那么我們就在學(xué)習(xí)python的裝飾器后就不用批量的在每個(gè)函數(shù)前面添加代碼了柱衔,而是使用python的裝飾器。

例子
s.py文件修改問(wèn)如下所示:

def outer(func):
    def inner():
        print('hello world!')
        return func()
    return inner

@outer
def f1():
    print('裝飾器')
    print('f1')

@outer
def f2():
    print('裝飾器')
    print('f2')

@outer
def f100():
    print('裝飾器')
    print('f3')

然后執(zhí)行test.py文件愉棱,輸出結(jié)果如下:

hello world!
裝飾器
f1
hello world!
裝飾器
f2
hello world!
裝飾器
f3

分析:相比之前的代碼唆铐,在test.py文件中調(diào)用f1f2f奔滑,f3函數(shù)的時(shí)除了打印原本的兩條語(yǔ)句之前還另外在執(zhí)行兩條語(yǔ)句之前還執(zhí)行了艾岂,print('hello world!')語(yǔ)句。這是為什么呢朋其?這是什么原理呢王浴?

首先我們要清楚,在python中函數(shù)是可以賦值給其他變量的梅猿,也就是和c語(yǔ)言中的函數(shù)指針差不多氓辣,一個(gè)函數(shù)名本身就是值的這個(gè)函數(shù)的地址,既然是地址那么就是可以傳遞的袱蚓。如果我們有了函數(shù)的地址钞啸,那么也就調(diào)用這個(gè)函數(shù)了。

看下面的例子

>>> def fun1():          # 定義函數(shù)fun1
...     print('fun1')
...
>>> fun1()                 # 調(diào)用函數(shù)fun1
fun1                            # 調(diào)用函數(shù)fun1 輸出 ‘fun1’
>>> fun2                     # 然后輸出看一下  fun2 變量
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'fun2' is not defined #  輸出fun2時(shí)候 報(bào)錯(cuò)  沒(méi)有定義
>>> fun1                                       #  輸出查看fun1 變量為:0x10a7fee18
<function fun1 at 0x10a7fee18>
>>> fun2 = fun1                            #  把fun1 變量的值賦值給 fun2
>>> fun2                                       # 輸出fun2 發(fā)現(xiàn)和fun1的值一模一樣
<function fun1 at 0x10a7fee18>
>>> fun2()                          # 把fun2當(dāng)作函數(shù)調(diào)用盡然功能和fun1一模一樣
fun1

從以上的例子可以看出來(lái)函數(shù)名可以通過(guò)賦值操作把地址賦值給其他變量并用通過(guò)其他變量也可以調(diào)用。把這個(gè)例子再升級(jí)一下:

>>> def fun3(f):   # 定義一個(gè)函數(shù)体斩,fun3
...     print(f)         #  輸出參數(shù)f
...     f()                # 特別注意這里梭稚,在前面說(shuō)到了一個(gè)變量是可以被賦值成一個(gè)函數(shù)
... #的地址的,這里的f就是被看成了被賦值成函數(shù)地址了硕勿,然后通過(guò)f被賦值的地址調(diào)用那個(gè)函數(shù)
>>> fun3(fun1)   #  這里在調(diào)用fun3的時(shí)候把fun1函數(shù)的地址傳遞給fun3的參數(shù)
<function fun1 at 0x10a7fee18>   # 可知輸出f變量的內(nèi)容正式上個(gè)例子中fun1函數(shù)的地址值
fun1

注意這個(gè)代碼是接這上面一個(gè)例子的

然后我們?cè)偕?jí)

>>> def fun4(f):    #照常我們定義一個(gè)普通的函數(shù)哨毁,參數(shù)是f
...     def fun5_in_fun4(): #在函數(shù)fun4內(nèi)又定義一個(gè)函數(shù)fun5_in_fun4
...             print('fun5_in_fun4') # 函數(shù)fun5_in_fun4體內(nèi)的語(yǔ)句輸出'fun5_in_fun4'字符串
...     f()            # 調(diào)用傳入的函數(shù)f  其實(shí)在下面?zhèn)魅氲倪€是fun1
...     return fun5_in_fun4  #特別注意這里,這里我們把在fun4內(nèi)定義的函數(shù)通過(guò)返回值返回
... 
>>> fun = fun4(fun1) # 調(diào)用fun4傳入fun1源武,這里用fun來(lái)接受fun4的返回值是'fun5_in_fun4'函數(shù)地址
fun1    # 在fun4還輸內(nèi)調(diào)用了傳入的函數(shù)fun1 輸出 'fun1'字符串
>>> fun()       #  在上一句代碼中fun接收了fun4的返回值是一個(gè)函數(shù)的地址扼褪,那么就可以調(diào)用fun4內(nèi)部定義的函數(shù)了
fun5_in_fun4    # 調(diào)用fun4內(nèi)部的函數(shù)輸出'fun5_in_fun4'字符串。

接著我們?cè)俑脑煲幌潞瘮?shù)fun4

>>> def outer(f):
...     def inner():
...             f()
...             print("fun5_in_fun4")
...             return 'function in fun4'
...     return inner
... 
>>> s = outer(fun1)
>>> string = s()
fun1
fun5_in_fun4
>>> string   # string 得到的返回值便是執(zhí)行函數(shù) inner 后返回的字符串
'function in fun4'

通過(guò)這樣一改造是不是就是我們最開(kāi)始例子中的python構(gòu)造器了
我們最開(kāi)始在定義函數(shù)outer的時(shí)候粱栖,估摸著會(huì)傳入一個(gè)函數(shù)话浇,然后在outer函數(shù)內(nèi)部重新定義了一個(gè)新的函數(shù)inner,在inner函數(shù)內(nèi)部不僅調(diào)用了傳入的函數(shù)闹究,更是增加了新的代碼(print("fun5_in_fun4"))幔崖,并且還有新的返回值(return 'function in fun4')。
更重要的是在outer函數(shù)中把內(nèi)部函數(shù)inner通過(guò)返回值的方式返回
我們?cè)谡{(diào)用函數(shù)outer的時(shí)候也就通過(guò)變量s接受了outer的返回值渣淤,接下里調(diào)用s赏寇,并接受返回值。

通過(guò)這樣一個(gè)過(guò)程我們便熟悉了python裝飾器的原理价认,這里我們便可以重新倒回去看嗅定,文章最開(kāi)始的例子了。

我們?cè)趫?zhí)行文件test.py用踩,調(diào)用s.f1()渠退,這里調(diào)用的f1,已經(jīng)不是前的f1了脐彩,從以上的分析中我們可以得知碎乃,其實(shí)這里調(diào)用的就是outer函數(shù)的內(nèi)部函數(shù)inner了。
至于為什么調(diào)用的函數(shù)為什么不是原來(lái)的f1 而是內(nèi)部函數(shù)inner那這就要關(guān)系到@outer了惠奸,@outer的功能也就是把f1函數(shù)替換為內(nèi)部函數(shù)inner

接下來(lái)我們?cè)敿?xì)說(shuō)一說(shuō)@outer的具體功能:

首先@outer的用法是:用符號(hào)@+函數(shù)名并放在一個(gè)函數(shù)定義的前面
@outer有兩個(gè)主要的功能:

  1. 自動(dòng)的執(zhí)行outer函數(shù)梅誓,并把下面的函數(shù)f1當(dāng)作參數(shù)傳遞給函數(shù)outer
    修改文件s.py如下:
def outer(func):
    print('before')
    print(func)
    def inner():
        print('hello world!')
        return func()
    return inner

@outer
def f1():
    print('f1')

然后運(yùn)行文件s.py然后看到輸出了字符串before<function f1 at 0x10f2cfd08>

  1. outer函數(shù)的返回值重新賦值給f1
    重新修改文件s.py如下所示:
def outer(func):

    return 'return' #  把返回值賦值給f1

@outer
def f1():
    print('f1')

print(f1)

運(yùn)行s.py可知佛南,打印出了'return'字符串证九。

python裝飾器的參數(shù)傳遞

看這樣一個(gè)例子:
修改文件s.py如下所示:

def outer(func):
    def inner():     # 沒(méi)有參數(shù)
        ret = func()   # 沒(méi)有參數(shù)
        print('hello')
        return ret
    return inner


@outer
def f1(a):     # 有一個(gè)參數(shù) a
    print('a:',a)
    return 'f1'

修改文件test.py如圖所示:

import s

s.f1(1)

執(zhí)行test.py文件,但是報(bào)錯(cuò)了共虑。這是為什么呢愧怜?
我們先根據(jù)原理分析下,在test.py文件中到我們調(diào)用s.py文件中的f1函數(shù)時(shí)妈拌,實(shí)際上是調(diào)用的函數(shù)inner拥坛,觀察源代碼蓬蝶,可以發(fā)現(xiàn)inner函數(shù)并沒(méi)有參數(shù),但是在調(diào)用的時(shí)候卻傳遞給了一個(gè)參數(shù)猜惋,故就報(bào)錯(cuò)了丸氛,那怎么解決這個(gè)問(wèn)題呢?
如果是缺少參數(shù)著摔,那我們?cè)趇nner函數(shù)中加上參數(shù)就行了缓窜。修改s.py中的代碼如下:

def outer(func):
    def inner(arg):
        ret = func(arg)
        print('hello')
        return ret
    return inner


@outer
def f1(a):
    print('a:',a)
    return 'f1'

這樣我們?cè)龠\(yùn)行test.py文件就不會(huì)報(bào)錯(cuò)了,但是這樣還是會(huì)有問(wèn)題谍咆!
s.py文件中增加一個(gè)函數(shù)f2,如下所示:

def outer(func):
    def inner(arg):
        ret = func(arg)
        print('hello')
        return ret
    return inner


@outer
def f1(a):
    print('a:',a)
    return 'f1'


@outer
def f2(a,b,c):
    print('a:',a,' b:',b," c:",c)
    return 'f1'

接著在test.py中調(diào)用f2函數(shù)禾锤,修改test.py文件如下:

import s

s.f1(1)
s.f2(1,2,3)

然后運(yùn)行test.py則還是會(huì)報(bào)一個(gè)參數(shù)錯(cuò)誤,在f1中只有一個(gè)參數(shù)摹察,而f2中則有三個(gè)參數(shù)恩掷,這應(yīng)該怎么覺(jué)得呢?
這里肯定是不能修改原函數(shù)f1f2的供嚎,那就只有修改outer函數(shù)了黄娘,既然是參數(shù)問(wèn)題,就自然應(yīng)該是inner函數(shù)出了問(wèn)題克滴,這里就應(yīng)該是修改inner函數(shù)的參數(shù)了逼争,這里就應(yīng)該用python中的萬(wàn)能參數(shù)了
修改s.py 文件如下所示:

def outer(func):
    def inner(*args, **kwargs):    # 使用python的萬(wàn)能參數(shù)
        ret = func(*args, **kwargs)
        print('hello')
        return ret
    return inner


@outer
def f1(a):
    print('a:',a)
    return 'f1'


@outer
def f2(a,b,c):
    print('a:',a,' b:',b," c:",c)
    return 'f1'

python裝飾器的應(yīng)用范圍

從以上講解我們明白了裝飾器的執(zhí)行原理劝赔,裝飾器的功能是修飾一個(gè)函數(shù)誓焦,也就是當(dāng)我們休要在某個(gè)函數(shù)體運(yùn)行之前或者是運(yùn)行之后添加某個(gè)功能,而不用修改原函數(shù)望忆,那我們就可以使用python的裝飾器罩阵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竿秆,一起剝皮案震驚了整個(gè)濱河市启摄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幽钢,老刑警劉巖歉备,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異匪燕,居然都是意外死亡蕾羊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)帽驯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)龟再,“玉大人,你說(shuō)我怎么就攤上這事尼变±眨” “怎么了浆劲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)哀澈。 經(jīng)常有香客問(wèn)我牌借,道長(zhǎng),這世上最難降的妖魔是什么割按? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任膨报,我火速辦了婚禮,結(jié)果婚禮上适荣,老公的妹妹穿的比我還像新娘现柠。我一直安慰自己,他們只是感情好束凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布晒旅。 她就那樣靜靜地躺著,像睡著了一般汪诉。 火紅的嫁衣襯著肌膚如雪废恋。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天扒寄,我揣著相機(jī)與錄音鱼鼓,去河邊找鬼。 笑死迄本,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的课竣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼于樟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了迂曲?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤路捧,失蹤者是張志新(化名)和其女友劉穎关霸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體杰扫,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队寇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了章姓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佳遣。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炭序,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苍日,到底是詐尸還是另有隱情惭聂,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布相恃,位于F島的核電站辜纲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拦耐。R本人自食惡果不足惜耕腾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杀糯。 院中可真熱鬧扫俺,春花似錦、人聲如沸固翰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骂际。三九已至疗琉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歉铝,已是汗流浹背盈简。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留太示,地道東北人柠贤。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像类缤,于是被迫代替她去往敵國(guó)和親臼勉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 一呀非、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,723評(píng)論 0 10
  • 包(lib)坚俗、模塊(module) 在Python中镜盯,存在包和模塊兩個(gè)常見(jiàn)概念岸裙。 模塊:編寫(xiě)Python代碼的py...
    清清子衿木子水心閱讀 3,801評(píng)論 0 27
  • 問(wèn)佛:這世界最完美的人是誰(shuí)?佛無(wú)語(yǔ)艺糜。 既然沒(méi)有完美幢尚?那世人那么多思想尉剩,主義,觀點(diǎn)理茎,針?shù)h相對(duì),你死我活皂林,究竟有何意義...
    得一物閱讀 252評(píng)論 0 0
  • 秋風(fēng)搖落了一粒松籽蚯撩,它無(wú)法選擇地墜落在一片林子里。 到了來(lái)年春天胎挎,它吐出了一莖嫩芽。不過(guò)美浦,很可惜,...
    淡淡煙閱讀 267評(píng)論 2 0
  • 今天是觀音菩薩生日浦辨,剛剛寫(xiě)下的時(shí)候突然想起來(lái)自己有一本小說(shuō)沼沈,和一本筆信集沒(méi)寫(xiě)完。哎芽腾,自律帶來(lái)自由呀。和我爸溝通了很...
    曼本竹心llm不良帥閱讀 143評(píng)論 0 1