python必學(xué)知識(shí)之裝飾器詳解
1.函數(shù)引用的概念
理解裝飾器之前先要理解函數(shù)引用的概念
def func():
print("hello world!")
# 調(diào)用函數(shù)
func()
# 引用函數(shù)
ret = func
print(id(ret))
print(id(func))
# 通過(guò)引用調(diào)用函數(shù)
ret()運(yùn)行結(jié)果ret和func的id相同,python里一切皆對(duì)象,函數(shù)名也是一個(gè)對(duì)象,ret是func函數(shù)的引用,它也指向func函數(shù)?;蛘呤呛瘮?shù)名僅僅是個(gè)變量,只不過(guò)指向了定義的函數(shù)而已,所以才能通過(guò) 函數(shù)名() 調(diào)用
2.理解閉包的概念
理解:先定義一個(gè)函數(shù),然后在該函數(shù)內(nèi)部再定義一個(gè)函數(shù),并且這個(gè)函數(shù)用到了外邊函數(shù)的變量,那么將這個(gè)函數(shù)以及用到的一些變量稱(chēng)之為閉包
# 定義一個(gè)函數(shù)
def func(num1):
# 在函數(shù)內(nèi)部再定義一個(gè)函數(shù),并且這個(gè)函數(shù)用到了外邊函數(shù)的變量,那么將這個(gè)函數(shù)以及用到的一些變量稱(chēng)之為閉包
def wrapper(num2):
# 在python3中,如果要修改外部函數(shù)的變量,需要加一條申明: nonlocal 外部變量名
# nonlocal num1
# num += 1
print("%s * %s 的積是%s" % (num1, num2, num1 * num2))
return num1 * num2
# 其實(shí)這里返回的就是閉包的結(jié)果,即返回內(nèi)層函數(shù)的引用
return wrapper
# 給func函數(shù)賦值,這個(gè)20就是給參數(shù)num1
ret = func(20) # 等價(jià)于ret = wrapper
# 注意這里的100其實(shí)給參數(shù)num2
print(ret(100)) # 等同于 print(wrapper(100))運(yùn)行結(jié)果將會(huì)顯示 20 * 100 的積是2000 注意點(diǎn): 由于閉包引用了外部函數(shù)的局部變量,則外部函數(shù)的局部變量沒(méi)有及時(shí)釋放,消耗內(nèi)存
3.裝飾器
裝飾器是程序開(kāi)發(fā)中經(jīng)常會(huì)用到的一個(gè)功能,用好了裝飾器,開(kāi)發(fā)效率如虎添翼,所以這也是Python面試中必問(wèn)的問(wèn)題。 假設(shè)下以下場(chǎng)景:
1.公司原本開(kāi)發(fā)了一套軟件,各個(gè)部門(mén)一起合作開(kāi)發(fā),目前公司有條不紊的進(jìn)行著,但是,以前基礎(chǔ)平臺(tái)的開(kāi)發(fā)人員在寫(xiě)代碼時(shí)候沒(méi)有關(guān)注驗(yàn)證相關(guān)的問(wèn)題,即需要增加以下功能:在所有功能執(zhí)行前,先進(jìn)行權(quán)限驗(yàn)證。
2.老大把工作交給 Low B,他是這么做的: 跟每個(gè)業(yè)務(wù)部門(mén)交涉,每個(gè)業(yè)務(wù)部門(mén)自己寫(xiě)代碼,調(diào)用基礎(chǔ)平臺(tái)的功能之前先驗(yàn)證。這樣一來(lái)基礎(chǔ)平臺(tái)就不需要做任何修改了。
太棒了,有充足的時(shí)間泡妹子… 當(dāng)天Low B 被開(kāi)除了…
3.老大把工作交給 Low BB,他是這么做的:
############### 基礎(chǔ)平臺(tái)提供的功能如下 ###############
def f1():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f1')
def f2():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f2')
def f3():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f3')
def f4():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
print('f4')
############### 業(yè)務(wù)部門(mén)不變 ###############
### 業(yè)務(wù)部門(mén)A 調(diào)用基礎(chǔ)平臺(tái)提供的功能###
f1()
f2()
f3()
f4()
### 業(yè)務(wù)部門(mén)B 調(diào)用基礎(chǔ)平臺(tái)提供的功能 ###
f1()
f2()
f3()
f4()
過(guò)了一周 Low BB 被開(kāi)除了…4.老大把工作交給 Low BBB,他是這么做的: 只對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行重構(gòu),其他業(yè)務(wù)部門(mén)無(wú)需做任何修改
############### 基礎(chǔ)平臺(tái)提供的功能如下 ###############
def check_login():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
pass
def f1():
check_login()
print('f1')
def f2():
check_login()
print('f2')
def f3():
check_login()
print('f3')
def f4():
check_login()
print('f4')老大看了下Low BBB 的實(shí)現(xiàn),嘴角漏出了一絲的欣慰的笑,語(yǔ)重心長(zhǎng)的跟Low BBB聊了個(gè)天:
5.老大說(shuō): 寫(xiě)代碼要遵循開(kāi)放封閉原則,雖然在這個(gè)原則是用的面向?qū)ο箝_(kāi)發(fā),但是也適用于函數(shù)式編程,簡(jiǎn)單來(lái)說(shuō),它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改,但可以被擴(kuò)展,即:
- 封閉:已實(shí)現(xiàn)的功能代碼塊
- 開(kāi)放:對(duì)擴(kuò)展開(kāi)發(fā)
如果將開(kāi)放封閉原則應(yīng)用在上述需求中,那么就不允許在函數(shù) f1 、f2、f3、f4的內(nèi)部進(jìn)行修改代碼,老板就給了Low BBB一個(gè)實(shí)現(xiàn)方案:
def w1(func):
def inner():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')對(duì)于上述代碼,也是僅僅對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行修改,就可以實(shí)現(xiàn)在其他人調(diào)用函數(shù) f1 f2 f3 f4 之前都進(jìn)行【驗(yàn)證】操作,并且其他業(yè)務(wù)部門(mén)無(wú)需做任何操作。
4.詳解裝飾器
4.1 裝飾器執(zhí)行流程

4.2 帶多個(gè)參數(shù)的裝飾器
例如記錄某個(gè)函數(shù)的執(zhí)行時(shí)間
import time
def timer(func):
def inner(*args,**kwargs):
start = time.time()
re = func(*args,**kwargs)
print(time.time() - start)
return re
return inner
@timer #==> func2 = timer(func2)
def func2(a):
print('in func2 and get a:%s'%(a))
return 'fun2 over'
func2('aaaaaa')
print(func2('aaaaaa'))4.3 給裝飾器帶參數(shù)
def outer(flag):
def timer(func):
def inner(*args,**kwargs):
if flag:
print('''執(zhí)行函數(shù)之前要做的''')
re = func(*args,**kwargs)
if flag:
print('''執(zhí)行函數(shù)之后要做的''')
return re
return inner
return timer
# 下面的裝飾過(guò)程
# 1. 調(diào)用outer(False)
# 2. 將步驟1得到的返回值,即timer返回, 然后timer(func),這里的func指向定義的func函數(shù)
# 3. 將timer(func)的結(jié)果返回,即inner
# 4. 讓func = inner,即func現(xiàn)在指向inner
@outer(False)
def func():
print(111)
func()調(diào)用時(shí)候的func()可以理解為:func() ====> outer(Flase)(func)()
4.4裝飾器的功能:
在不修改原函數(shù)及其調(diào)用方式的情況下對(duì)原函數(shù)功能進(jìn)行擴(kuò)展。
4.5.裝飾器應(yīng)用場(chǎng)景:
(1)引入日志
(2)函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)
(3)執(zhí)行函數(shù)前預(yù)備處理
(4)執(zhí)行函數(shù)后清理功能
(5)權(quán)限校驗(yàn)等場(chǎng)景
(6)緩存
4.6 functools的應(yīng)用
正常我們情況下查看函數(shù)的一些信息的方法在此處都會(huì)失效
def outer(func):
def inner(*args,**kwargs):
"""hello world"""
return func(*args,**kwargs)
return inner
@outer
def index():
'''你好,世界'''
print('hello world!')
print("函數(shù)注釋?zhuān)?s" % index.__doc__) #查看函數(shù)注釋的方法
print("函數(shù)名:%s" % index.__name__) #查看函數(shù)名的方法上述代碼返回的結(jié)果是:

很顯然,這不是我們需要的結(jié)果。我們希望得到的是被裝飾的函數(shù)的函數(shù)名和注釋。
functools的wraps能夠?qū)⒃械暮瘮?shù)名返回,需要使用functools.wraps在裝飾器中的函數(shù)上把傳進(jìn)來(lái)的這個(gè)函數(shù)進(jìn)行一個(gè)包裹,這樣就不會(huì)丟失原來(lái)的函數(shù)的__name__等屬性.
from functools import wraps
def outer(func):
@wraps(func)
def inner(*args,**kwargs):
"""hello world"""
return func(*args,**kwargs)
return inner
@outer
def index():
'''你好,世界'''
print('hello world!')
print("函數(shù)注釋?zhuān)?s" % index.__doc__) #查看函數(shù)注釋的方法
print("函數(shù)名:%s" % index.__name__) #查看函數(shù)名的方法運(yùn)行結(jié)果為:

5.類(lèi)裝飾器
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---裝飾器中的功能---")
self.__func()
#說(shuō)明:
#1. 當(dāng)用Test來(lái)裝作裝飾器對(duì)test函數(shù)進(jìn)行裝飾的時(shí)候,首先會(huì)創(chuàng)建Test的實(shí)例對(duì)象
# 并且會(huì)把test這個(gè)函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中
# 即在__init__方法中的屬性__func指向了test指向的函數(shù)
#
#2. test指向了用Test創(chuàng)建出來(lái)的實(shí)例對(duì)象
#
#3. 當(dāng)在使用test()進(jìn)行調(diào)用時(shí),就相當(dāng)于讓這個(gè)對(duì)象(),因此會(huì)調(diào)用這個(gè)對(duì)象的__call__方法
#
#4. 為了能夠在__call__方法中調(diào)用原來(lái)test指向的函數(shù)體,所以在__init__方法中就需要一個(gè)實(shí)例屬性來(lái)保存這個(gè)函數(shù)體的引用
# 所以才有了self.__func = func這句代碼,從而在調(diào)用__call__方法中能夠調(diào)用到test之前的函數(shù)體
@Test
def test():
print("----test---")
test()
showpy()#如果把這句話注釋?zhuān)匦逻\(yùn)行程序,依然會(huì)看到"--初始化--"運(yùn)行結(jié)果如下:
---初始化---
func name is test
---裝飾器中的功能---
----test---
到此這篇關(guān)于python必學(xué)知識(shí)之裝飾器詳解的文章就介紹到這了,更多相關(guān)python裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python爬蟲(chóng)框架scrapy實(shí)現(xiàn)downloader_middleware設(shè)置proxy代理功能示例
這篇文章主要介紹了Python爬蟲(chóng)框架scrapy實(shí)現(xiàn)downloader_middleware設(shè)置proxy代理功能,結(jié)合實(shí)例形式分析了scrapy框架proxy代理設(shè)置技巧與相關(guān)問(wèn)題注意事項(xiàng),需要的朋友可以參考下2018-08-08
如何使用Python的Requests包實(shí)現(xiàn)模擬登陸
這篇文章主要為大家詳細(xì)介紹了使用Python的Requests包模擬登陸,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
TensorFlow2.0使用keras訓(xùn)練模型的實(shí)現(xiàn)
這篇文章主要介紹了TensorFlow2.0使用keras訓(xùn)練模型的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
python解析中國(guó)天氣網(wǎng)的天氣數(shù)據(jù)
最近學(xué)習(xí)python 感覺(jué)這門(mén)腳本語(yǔ)言十分靈活 而且功能十分強(qiáng)大 尤其是他re庫(kù)用于正則匹配十分強(qiáng)大,寫(xiě)了個(gè)例子解析中國(guó)天氣網(wǎng)2014-03-03
深度Q網(wǎng)絡(luò)DQN(Deep Q-Network)強(qiáng)化學(xué)習(xí)的原理與實(shí)戰(zhàn)
深度Q學(xué)習(xí)將深度神經(jīng)網(wǎng)絡(luò)與強(qiáng)化學(xué)習(xí)相結(jié)合,解決了傳統(tǒng)Q學(xué)習(xí)在高維狀態(tài)空間下的局限性,通過(guò)經(jīng)驗(yàn)回放和目標(biāo)網(wǎng)絡(luò)等技術(shù),DQN能夠在復(fù)雜環(huán)境中學(xué)習(xí)有效的策略,本文通過(guò)CartPole環(huán)境的完整實(shí)現(xiàn),展示了DQN的核心思想和實(shí)現(xiàn)細(xì)節(jié)2025-04-04
詳解如何使用opencv實(shí)現(xiàn)圖片相似度檢測(cè)
這篇文章主要為大家詳細(xì)介紹了如何使用opencv實(shí)現(xiàn)圖片相似度檢測(cè),文中的示例代碼講解詳細(xì),對(duì)于我們學(xué)習(xí)人工智能有一定的幫助,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12
python超詳細(xì)實(shí)現(xiàn)完整學(xué)生成績(jī)管理系統(tǒng)
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)完整版學(xué)生成績(jī)管理系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2022-03-03

