我們要完全理解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è)文件:
__inti__.py
s.py
-
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)用f1
,f2f
奔滑,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è)主要的功能:
- 自動(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>
- 將
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ù)f1
和f2
的供嚎,那就只有修改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的裝飾器罩阵。