Python裝飾器-閉包與函數(shù)裝飾器
一、閉包
在學(xué)習(xí)裝飾器前,需要先了解閉包的概念。形成閉包的要點(diǎn):
- 函數(shù)嵌套
- 將內(nèi)部函數(shù)作為外部函數(shù)的返回值
- 內(nèi)部函數(shù)必須要使用到外部函數(shù)的變量
下面以一個(gè)計(jì)算列表平均值的案例來(lái)講解閉包:
- 首先,定義一個(gè)函數(shù)make_average;
- 其次,在make_average函數(shù)內(nèi)定義一個(gè)空列表,用來(lái)存儲(chǔ)數(shù)值;
- 再次,定義一個(gè)內(nèi)部函數(shù),用來(lái)計(jì)算列表平均值;
- 最后,將這個(gè)內(nèi)函數(shù)作為外函數(shù)make_average的返回值,注意不要加( ),加( )就變成調(diào)用這個(gè)函數(shù)了。
運(yùn)行結(jié)果如下:當(dāng)傳入的數(shù)值為20時(shí),列表中只有一個(gè)數(shù),所以計(jì)算結(jié)果是20;當(dāng)再傳入一個(gè)數(shù)值30時(shí),此時(shí)列表中就有兩個(gè)數(shù)20和30,所以平均值的計(jì)算結(jié)果是25.
二、裝飾器
1.裝飾器引入
例如,有以下兩個(gè)函數(shù),分別計(jì)算兩個(gè)數(shù)的和以及成績(jī):
現(xiàn)在有個(gè)需求:我想要在每個(gè)函數(shù)的計(jì)算開(kāi)始前打印“開(kāi)始計(jì)算...”,在計(jì)算結(jié)束后打印“計(jì)算結(jié)束...”。我們可以通過(guò)直接修改函數(shù)代碼的方式來(lái)滿(mǎn)足這個(gè)需求,但這樣會(huì)面臨以下問(wèn)題:
- 如果要修改的函數(shù)過(guò)多,十個(gè)甚至一百個(gè)函數(shù),未免不現(xiàn)實(shí);
- 不便于后期維護(hù),例如我不想打印“開(kāi)始計(jì)算...”了,而是要打印“begin...”,豈不是又要重新修改一遍;
- 違反開(kāi)閉原則(OCP),即程序的設(shè)計(jì),要求開(kāi)放對(duì)程序的擴(kuò)展、關(guān)閉對(duì)程序的修改;
所以,上述直接修改函數(shù)代碼的方式不可行。我們希望在不修改原函數(shù)的情況下,實(shí)現(xiàn)對(duì)函數(shù)的擴(kuò)展。例如:
執(zhí)行結(jié)果如下:
這種新創(chuàng)建一個(gè)函數(shù)的方式雖然沒(méi)有修改原函數(shù),但面臨一個(gè)很?chē)?yán)重的問(wèn)題:
只能擴(kuò)展指定函數(shù),不能通用于其它函數(shù),例如擴(kuò)展上述的add函數(shù),而不能擴(kuò)展mul函數(shù),如果想要擴(kuò)展mul函數(shù),只能再創(chuàng)建一個(gè)擴(kuò)展函數(shù);
因?yàn)椋覀兿M梢远x一個(gè)通用的擴(kuò)展函數(shù),可以作用域所有函數(shù)。這類(lèi)不改變?cè)瘮?shù)代碼的通用函數(shù)就是:裝飾器。
2.函數(shù)裝飾器
裝飾器本質(zhì)上是一個(gè)python函數(shù)或類(lèi),它可以讓其他函數(shù)或類(lèi)在不需要做任何代碼修改的前提下增加額外功能,也就是為已經(jīng)存在的對(duì)象添加額外功能,裝飾器的返回值也是一個(gè)函數(shù)/類(lèi)對(duì)象。它經(jīng)常用于有切面需求的場(chǎng)景,比如:插入日志、性能測(cè)試、事務(wù)處理、緩存、權(quán)限校驗(yàn)等場(chǎng)景。
1)被裝飾函數(shù)不帶參數(shù)
例如:
運(yùn)行結(jié)果如下:
可見(jiàn),在沒(méi)有改變?cè)瘮?shù)代碼的情況下,即給原函數(shù)增加了一些額外的功能,func是要修飾的函數(shù),作為一個(gè)變量傳入裝飾函數(shù),能夠通用于其他函數(shù),這個(gè)wrapper_info就是裝飾器。但目前面臨的問(wèn)題是,被裝飾函數(shù)如果帶參數(shù)怎么辦?例如:
2)被裝飾函數(shù)帶參數(shù)
盡管可以在裝飾器wrapper_info中傳入name、age,但并不是每個(gè)被裝飾的函數(shù)都只有name、age,亦或是指定類(lèi)型的參數(shù),還有可能傳入的是字典、列表、元組等。也就是傳入?yún)?shù)的類(lèi)型和數(shù)量不固定怎么辦?
此時(shí)就需要用到不定長(zhǎng)參數(shù):(*args, **kwargs)
例如:
運(yùn)行結(jié)果如下:
3)裝飾器帶參數(shù)
上述提到的是裝飾器,一種是應(yīng)用于被裝飾的函數(shù)不帶參數(shù),一種是被裝飾的函數(shù)帶參數(shù),那裝飾器本身能否帶參數(shù)呢?比如我定義一個(gè)變量,想通過(guò)傳入不同的值來(lái)控制這個(gè)裝飾器實(shí)現(xiàn)不同的功能。答案是肯定的,例如:
運(yùn)行結(jié)果如下:
3.裝飾器調(diào)用
方式一:以函數(shù)方式調(diào)用
如果是裝飾器函數(shù)帶參數(shù),則調(diào)用方式為:
方式二:以語(yǔ)法糖方式調(diào)用
即在被裝飾函數(shù)上方以@符號(hào)進(jìn)行修飾
如果是裝飾器函數(shù)帶參數(shù),例如上述的use_log,則需要在裝飾器中傳入?yún)?shù):
小結(jié)
什么是裝飾器?
在不改變?cè)瘮?shù)代碼的情況下,給原函數(shù)增加了一些額外的功能,并且能夠通用于其他函數(shù),這樣的函數(shù)就稱(chēng)作為裝飾器。
裝飾器的調(diào)用
可以通過(guò)傳統(tǒng)調(diào)用函數(shù)的方式進(jìn)行調(diào)用,也可以通過(guò)@裝飾器的方式調(diào)用
裝飾器的特點(diǎn)
- 通過(guò)裝飾器,可以在不修改原來(lái)函數(shù)的情況下對(duì)函數(shù)進(jìn)行擴(kuò)展
- 一個(gè)函數(shù)可以同時(shí)指定多個(gè)裝飾器