裝飾器是從英文decorator翻譯過來的,從字面上來看就是對某個東西進(jìn)行修飾,增強(qiáng)被修飾物的功能,下面我們對裝飾器做下簡單介紹。
成都創(chuàng)新互聯(lián)公司是一家專注于網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)與策劃設(shè)計(jì),潁東網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:潁東等地區(qū)。潁東做網(wǎng)站價(jià)格咨詢:18980820575
一、怎么編寫裝飾器
裝飾器的實(shí)現(xiàn)很簡單,本質(zhì)是一個可調(diào)用對象,可以是函數(shù)、方法、對象等,它既可以裝飾函數(shù)也可以裝飾類和方法,為了簡單說明問題,我們實(shí)現(xiàn)一個函數(shù)裝飾器,如下代碼:
有了這個裝飾器,我們就可以打印出什么時(shí)候開始和結(jié)束調(diào)用函數(shù),對于排查函數(shù)的調(diào)用鏈非常方便。
二、帶參數(shù)的裝飾器
上面的例子無論什么時(shí)候調(diào)用sum都會輸出信息,如果我們需要按需輸出信息怎么實(shí)現(xiàn)呢,這時(shí)就要用到帶參數(shù)的裝飾器了,如下代碼:
對sum使用裝飾器時(shí)沒有參數(shù),這時(shí)debug為0,所以調(diào)用sum時(shí)不會輸出函數(shù)調(diào)用相關(guān)信息。
對multi使用裝飾器時(shí)有參數(shù),這時(shí)debug為1,所以調(diào)用multi時(shí)會輸出函數(shù)調(diào)用相關(guān)信息。
三、函數(shù)名字問題
當(dāng)我們打印被裝飾后的函數(shù)名字時(shí),不知道大家有沒發(fā)現(xiàn)輸出的不是函數(shù)本身的名字,如下代碼會輸出‘wrap’而不是‘sum’:
有時(shí)這種表現(xiàn)并不是我們想要的,我們希望被裝飾后的函數(shù)名字還是函數(shù)本身,那要怎么實(shí)現(xiàn)呢?很簡單,只需要引入functools.wraps即可,如下代碼就會輸出‘sum’了:
看完后是不是覺得python裝飾器很簡單,只要了解它的本質(zhì),怎么寫都行,有好多種玩法呢。
裝飾器是通過裝飾器函數(shù)修改原函數(shù)的一些功能而不需要修改原函數(shù),在很多場景可以用到它,比如① 執(zhí)行某個測試用例之前,判斷是否需要登錄或者執(zhí)行某些特定操作;② 統(tǒng)計(jì)某個函數(shù)的執(zhí)行時(shí)間;③ 判斷輸入合法性等。合理使用裝飾器可以極大地提高程序的可讀性以及運(yùn)行效率。本文將介紹Python裝飾器的使用方法。
python裝飾器可以定義如下:
輸出:
python解釋器將test_decorator函數(shù)作為參數(shù)傳遞給my_decorator函數(shù),并指向了內(nèi)部函數(shù) wrapper(),內(nèi)部函數(shù) wrapper() 又會調(diào)用原函數(shù) test_decorator(),所以decorator()的執(zhí)行會先打印'this is wrapper',然后打印'hello world', test_decorator()執(zhí)行完成后,打印 'bye' ,*args和**kwargs,表示接受任意數(shù)量和類型的參數(shù)。
裝飾器 my_decorator() 把真正需要執(zhí)行的函數(shù) test_decorator() 包裹在其中,并且改變了它的行為,但是原函數(shù) test_decorator() 不變。
一般使用如下形式使用裝飾器:
@my_decorator就相當(dāng)于 decorator = my_decorator(test_decorator) 語句。
內(nèi)置裝飾器@functools.wrap可用于保留原函數(shù)的元信息(將原函數(shù)的元信息,拷貝到對應(yīng)的裝飾器函數(shù)里)。先來看看沒有使用functools的情況:
輸出:
從上面的輸出可以看出test_decorator() 函數(shù)被裝飾以后元信息被wrapper() 函數(shù)取代了,可以使用@functools.wrap裝飾器保留原函數(shù)的元信息:
輸出:
裝飾器可以接受自定義參數(shù)。比如定義一個參數(shù)來設(shè)置裝飾器內(nèi)部函數(shù)的執(zhí)行次數(shù):
輸出:
Python 支持多個裝飾器嵌套:
裝飾的過程:
順序從里到外:
test_decorator('hello world') 執(zhí)行順序和裝飾的過程相反。
輸出:
類也可以作為裝飾器,類裝飾器主要依賴__call__()方法,是python中所有能被調(diào)用的對象具有的內(nèi)置方法(python魔術(shù)方法),每當(dāng)調(diào)用一個類的實(shí)例時(shí),__call__()就會被執(zhí)行一次。
下面的類裝飾器實(shí)現(xiàn)統(tǒng)計(jì)函數(shù)執(zhí)行次數(shù):
輸出:
下面介紹兩種裝飾器使用場景
統(tǒng)計(jì)函數(shù)執(zhí)行所花費(fèi)的時(shí)間
輸出:
在使用某些web服務(wù)時(shí),需要先判斷用戶是否登錄,如果沒有登錄就跳轉(zhuǎn)到登錄頁面或者提示用戶登錄:
--THE END--
1、 lru_cache
這個裝飾器來自functools模塊。該模塊包含在標(biāo)準(zhǔn)庫中,非常易于使用。它還包含比這個裝飾器更酷的功能,但這個裝飾器是非常受人喜歡的。此裝飾器可用于使用緩存加速函數(shù)的連續(xù)運(yùn)行。當(dāng)然,這應(yīng)該在使用時(shí)記住一些關(guān)于緩存的注意事項(xiàng),但在通用使用情況下,大多數(shù)時(shí)候這個裝飾器都是值得使用的。
2、JIT
JIT是即時(shí)編譯的縮寫。通常每當(dāng)我們在Python中運(yùn)行一些代碼時(shí),發(fā)生的第一件事就是編譯。這種編譯會產(chǎn)生一些開銷,因?yàn)轭愋捅环峙淞藘?nèi)存,并存儲為未分配但已命名的別名,使用即時(shí)編譯,我們在執(zhí)行時(shí)才進(jìn)行編譯。
在很多方面,我們可以將其視為類似于并行計(jì)算的東西,其中Python解釋器同時(shí)處理兩件事以節(jié)省時(shí)間。Numba JTI編譯器因?qū)⑦@一概念提到Python中而聞名,可以非常輕松地調(diào)用此裝飾器,并立即提高代碼的性能。Numba包提供了JIT裝飾器,它使運(yùn)行更密集的軟件變得更加容易,而不必進(jìn)入C。
3、do_twice
do_twice裝飾器的功能與它的名字差不多。此裝飾器可用于通過一次調(diào)用運(yùn)行兩次函數(shù),對調(diào)試特別有用。它可以用于測量兩個不同迭代的功能。
4、count_calls
count_calls裝飾器可用于提供有關(guān)函數(shù)在軟件中使用多少次的信息。與do_twice一樣,對調(diào)試也特別有用。
5、dataclass
為了節(jié)省編寫類的時(shí)間,推薦使用dataclass裝飾器。這個裝飾器可用于快速編寫類中常見的標(biāo)準(zhǔn)方法,這些方法通常會在我們編寫的類中找到。
6、singleton
singleton是一個單例裝飾器。通常,單例裝飾器是由用戶自己編寫的,實(shí)際上并不是導(dǎo)入的。
7、use_unit
在科學(xué)計(jì)算中經(jīng)常派上用場的一種裝飾器是use_unit裝飾器。此裝飾器可用于更改返回結(jié)果的表示單位。這對于那些不想在數(shù)據(jù)中添加度量單位但仍希望人們知道這些單位是什么的人很有用。這個裝飾器可不是在任何模塊中真正有用,但它是非常常見的,對科學(xué)應(yīng)用程序非常有用。
裝飾器其實(shí)一直是我的一個"老大難"。這個知識點(diǎn)就放在那,但是拖延癥。。。
其實(shí)在平常寫寫腳本的過程中,這個知識點(diǎn)你可能用到不多
但在面試的時(shí)候,這可是一個高頻問題。
所謂的裝飾器,其實(shí)就是通過裝飾器函數(shù),來修改原函數(shù)的一些功能,使得原函數(shù)不需要修改。
這一句話理解起來可能沒那么輕松,那先來看一個"傻瓜"函數(shù)。
放心,絕對不是"Hello World"!
怎么樣,沒騙你吧? 哈哈,這個函數(shù)不用運(yùn)行相信大家都知道輸出結(jié)果: "你好,裝飾器" 。
那如果我想讓 hello() 函數(shù)再實(shí)現(xiàn)個其他功能,比如多打印一句話。
那么,可以這樣"增強(qiáng)"一下:
運(yùn)行結(jié)果:
很顯然,這個"增強(qiáng)"沒啥作用,但是可以幫助理解裝飾器。
當(dāng)運(yùn)行最后的 hello() 函數(shù)時(shí),調(diào)用過程是這樣的:
那上述代碼里的 my_decorator() 就是一個裝飾器。
它改變了 hello() 的行為,但是并沒有去真正的改變 hello()函數(shù) 的內(nèi)部實(shí)現(xiàn)。
但是,python一直以"優(yōu)雅"被人追捧,而上述的代碼顯然不夠優(yōu)雅。
所以,想讓上述裝飾器變得優(yōu)雅,可以這樣寫:
這里的 @my_decorator 就相當(dāng)于舊代碼的 hello = my_decorator(hello) , @ 符號稱為語法糖。
那如果還有其他函數(shù)也需要加上類似的裝飾,直接在函數(shù)的上方加上 @my_decorator 就可以,大大提高函數(shù)
的重復(fù)利用與可讀性。
輸出:
上面的只是一個非常簡單的裝飾器,但是實(shí)際場景中,很多函數(shù)都是要帶有參數(shù)的,比如hello(people_name)。
其實(shí)也很簡單,要什么我們就給什么唄,直接在對應(yīng)裝飾器的 wrapper() 上,加上對應(yīng)的參數(shù):
輸出:
但是還沒完,這樣雖然簡單,但是隨之而來另一個問題:因?yàn)椴⒉皇撬泻瘮?shù)參數(shù)都是一樣的,
當(dāng)其他要使用裝飾器的函數(shù)參數(shù)不止這個一個腫么辦?比如:
沒關(guān)系,在python里, *args 和 **kwargs 表示接受任意數(shù)量和類型的參數(shù),所以我們可以這樣
寫裝飾器里的 wrapper() 函數(shù):
同時(shí)運(yùn)行下 hello("老王") ,和 hello3("張三", "李四") ,看結(jié)果:
上面2種,裝飾器都是接收外來的參數(shù),其實(shí)裝飾器還可以接收自己的參數(shù)。
比如,我加個參數(shù)來控制下裝飾器中打印信息的次數(shù):
注意,這里 count 裝飾函數(shù)中的2個 return .
運(yùn)行下,應(yīng)該會出現(xiàn)3次:
現(xiàn)在多做一步 探索 ,我們來打印下下面例子中的hello()函數(shù)的元信息:
輸出:
這說明了,它不再是以前的那個 hello() 函數(shù),而是被 wrapper() 函數(shù)取代了。
如果我們需要用到元函數(shù)信息,那怎么保留它呢?這時(shí)候可以用內(nèi)置裝飾器 @functools.wrap 。
運(yùn)行下:
好記性不如爛筆頭,寫一下理解一下會好很多。
下面還分享類的裝飾器,以及裝飾器所用場景。