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ù)以及用到的一些變量稱之為閉包
# 定義一個(gè)函數(shù) def func(num1): # 在函數(shù)內(nèi)部再定義一個(gè)函數(shù),并且這個(gè)函數(shù)用到了外邊函數(shù)的變量,那么將這個(gè)函數(shù)以及用到的一些變量稱之為閉包 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è)部門一起合作開(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ù)部門交涉,每個(gè)業(yè)務(wù)部門自己寫(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ù)部門不變 ############### ### 業(yè)務(wù)部門A 調(diào)用基礎(chǔ)平臺(tái)提供的功能### f1() f2() f3() f4() ### 業(yè)務(wù)部門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ù)部門無(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ù)部門無(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ù)注釋:%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ù)注釋:%s" % index.__doc__) #查看函數(shù)注釋的方法 print("函數(shù)名:%s" % index.__name__) #查看函數(shù)名的方法
運(yùn)行結(jié)果為:
5.類裝飾器
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()#如果把這句話注釋,重新運(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-04TensorFlow2.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-02python解析中國(guó)天氣網(wǎng)的天氣數(shù)據(jù)
最近學(xué)習(xí)python 感覺(jué)這門腳本語(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-12python超詳細(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