Python - 裝飾器

一拓颓、裝飾器的基本使用

在不改變函數(shù)源代碼的前提下芙扎,給函數(shù)添加新的功能,這時(shí)就需要用到“裝飾器”牍白。

0.開(kāi)放封閉原則

寫(xiě)代碼要遵循開(kāi)放封閉原則,它規(guī)定“已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改抖棘,但可以被擴(kuò)展茂腥。”——封閉即是已經(jīng)實(shí)現(xiàn)的功能代碼塊切省,開(kāi)放是對(duì)擴(kuò)展開(kāi)放最岗。

1.裝飾器本質(zhì)

裝飾器本質(zhì)上就是一個(gè)閉包 ,但是是一個(gè)特殊的閉包朝捆。當(dāng)閉包函數(shù)有且只有一個(gè)參數(shù)般渡,并且該參數(shù)必須是函數(shù)類型,此時(shí)閉包才稱為“裝飾器”芙盘。

2.裝飾器的功能特點(diǎn)

1)給已有函數(shù)增加額外的功能驯用;

2)但是不修改已有函數(shù)的源代碼;

3)不修改已有函數(shù)的調(diào)用方式儒老;

3.裝飾器定義格式

# 定義裝飾器

def decorator(func):

? ? def wrapper(*args, **kwargs):

? ? ? ? 要添加的額外功能

? ? ? ? return func(*args, **kwargs)

? ? return wrapper

其中:

1)func:是一個(gè)函數(shù)(函數(shù)作為參數(shù))蝴乔;

2)wrapper:內(nèi)層閉包函數(shù),調(diào)用了func驮樊;

3)return wrapper:外層函數(shù)decorator返回值為內(nèi)層函數(shù)wrapper薇正;

4.裝飾器的使用

先定義要裝飾的函數(shù):

def print_info():

? ? print('打印的內(nèi)容……')

為函數(shù)print_info添加功能:打印前提示,打印后提示

# 定義裝飾器

def decorator(func):

? ? def wrapper(*args, **kwargs):

? ? ? ? print('***我要開(kāi)始打印正文了***')

? ? ? ? func()

? ? ? ? print('***全文已打印完畢***')

? ? ? ? return func(*args, **kwargs)

? ? return wrapper


# 使用裝飾器:將print_info函數(shù)作為裝飾器的參數(shù)

print_info = decorator(print_info)

print_info()

>>> 運(yùn)行結(jié)果

***我要開(kāi)始打印正文了***

打印的內(nèi)容……

***全文已打印完畢***

可以看出巩剖,1)為函數(shù)print_info添加功能成功铝穷;2)沒(méi)有修改原函數(shù)钠怯;3)原函數(shù)調(diào)用方式也沒(méi)變佳魔;

裝飾器內(nèi)部運(yùn)行原理:

說(shuō)明:

1)print_info = decorator(print_info),此處只裝飾函數(shù)晦炊,并且把裝飾器的內(nèi)層函數(shù)返回鞠鲜,并賦值給print_info,此時(shí)print_info實(shí)際已經(jīng)是wrapper函數(shù)断国;

2)print_info()贤姆,此處調(diào)用函數(shù)時(shí),才正式開(kāi)始執(zhí)行已經(jīng)添加功能的函數(shù)(wrapper函數(shù))稳衬;

3)wrapper函數(shù)中的func()調(diào)用的是原函數(shù)霞捡,因?yàn)殚]包函數(shù)能夠保存它使用的外層函數(shù)的變量;

5.注意事項(xiàng)

由上面可以看出裝飾原函數(shù)后薄疚,原函數(shù)print_info的函數(shù)名已經(jīng)變成wrapper(print_info.__name__='wrapper')碧信。

為了不改變?cè)瘮?shù)的函數(shù)名赊琳,需要把原始函數(shù)的__name__等屬性復(fù)制到wrapper()函數(shù)中,否則砰碴,有些依賴函數(shù)簽名的代碼執(zhí)行就會(huì)出錯(cuò)躏筏。Python內(nèi)置的裝飾器functools.wraps能夠?qū)崿F(xiàn)。因此嚴(yán)格意義上裝飾器的定義應(yīng)為:

import functools

def decorator(func):

? ? @functools.wraps(func)

? ? def wrapper(*args, **kwargs):

? ? ? ? 要添加的額外功能

? ? ? ? return func(*args, **kwargs)

? ? return wrapper

6.裝飾器的裝飾方式

方式一:直接調(diào)用

func = decorator(func)? # 直接調(diào)用裝飾器函數(shù)呈枉,傳入函數(shù)名趁尼,并賦值給函數(shù)名

但每次都寫(xiě)調(diào)用-賦值語(yǔ)句比較麻煩,于是Python提供了比較簡(jiǎn)單的第二種“語(yǔ)法糖”書(shū)寫(xiě)格式

方式二:語(yǔ)法糖--@裝飾器名字

@decorator? ? ? ? ? ? ? # 在函數(shù)定義語(yǔ)句上面直接“@裝飾器名字”

def func():

? ? ...

語(yǔ)法糖裝飾是常用的裝飾器裝飾方式猖辫,“@decorator”就等價(jià)于func = decorator(func)酥泞。

裝飾器在模塊進(jìn)行加載時(shí)就會(huì)立即執(zhí)行裝飾過(guò)程。

7.裝飾器使用場(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)緩存;

8.應(yīng)用舉例

統(tǒng)計(jì)函數(shù)的執(zhí)行時(shí)間:

# 統(tǒng)計(jì)函數(shù)執(zhí)行時(shí)間的裝飾器

import functools

import time

def execute_time(func):

? ? @functools.wraps(func)

? ? def wrapper(*args, **kwargs):

? ? ? ? # 程序開(kāi)始時(shí)間點(diǎn)

? ? ? ? start = time.time()

? ? ? ? # 執(zhí)行函數(shù)

? ? ? ? result = func(*args, **kwargs)

? ? ? ? # 程序結(jié)束時(shí)間點(diǎn)

? ? ? ? end = time.time()

? ? ? ? # 計(jì)算執(zhí)行時(shí)間

? ? ? ? execution_time = end - start

? ? ? ? print('{}執(zhí)行時(shí)間:{}s'.format(func.__name__, execution_time))

? ? ? ? # 返回函數(shù)結(jié)果

? ? ? ? return result

? ? return wrapper


# 定義一個(gè)簡(jiǎn)單的能夠指定循環(huán)次數(shù)的函數(shù)叹阔,并裝飾

@execute_time

def loop_count(n):

? ? # 循環(huán)打印

? ? for i in range(n):

? ? ? ? print('第{}次...'.format(i+1))?



# 看看循環(huán)1000000次需要多長(zhǎng)時(shí)間挠轴?

loop_count(1000000)

>>> 運(yùn)行結(jié)果:

第1次...

第2次...

...

第1000000次...

loop_count執(zhí)行時(shí)間:6.355999231338501s

二、通用裝飾器

要裝飾的函數(shù)可能不需要傳參耳幢,也可能需要傳參岸晦;然后傳多少個(gè)參數(shù),是位置傳參還是關(guān)鍵字傳參睛藻,有沒(méi)有返回值启上,這些都不能事先知道。也就是說(shuō)店印,不可能針對(duì)性地去定義裝飾器冈在。于是,就需要定義通用的裝飾器按摘。不論被裝飾的函數(shù)是否需要傳參包券,傳幾個(gè)參數(shù),怎么傳參炫贤,有無(wú)返回值溅固,通用裝飾器都能使用。

通用裝飾器的定義格式:

def decorator(func):

? ? def wrapper(*args, **kwargs):

? ? ? ? func執(zhí)行前要添加的額外功能

? ? ? ? result = func(*args, **args)

? ? ? ? func執(zhí)行后要添加的額外功能

? ? ? ? return result

? ? return wrapper

說(shuō)明:

1)內(nèi)層閉包函數(shù)使用*args兰珍、**kwargs接收參數(shù)侍郭,能夠應(yīng)對(duì)所有形式的不定長(zhǎng)傳參;

2)result為被裝飾函數(shù)的返回值,在閉包函數(shù)中return返回亮元;

解決了傳參和返回值的問(wèn)題汰寓,實(shí)現(xiàn)了通用性。

三苹粟、多個(gè)裝飾器

多個(gè)裝飾器同時(shí)裝飾一個(gè)函數(shù)

# 裝飾器1:將原函數(shù)輸入內(nèi)容用“()”括起

def add_little_sign(func):

? ? def wrapper1(*args, **kwargs):

? ? ? ? origin_info = func(*args, **kwargs)

? ? ? ? return '(' + origin_info + ')'

? ? return wrapper

# 裝飾器2:將原函數(shù)輸入內(nèi)容用“{}”括起

def add_big_sign(func):

? ? def wrapper2(*args, **kwargs):

? ? ? ? origin_info = func(*args, **kwargs)

? ? ? ? return '{' + origin_info + '}'

? ? return wrapper

# 被裝飾函數(shù):打印info

@add_big_sign? ? ? # 外層

@add_little_sign? ? ? # 內(nèi)層

def print_info(info):

? ? return info

# 調(diào)用裝飾后的函數(shù)

print(print_info('Hello World!'))

>>> 運(yùn)行結(jié)果:

{(Hello World!)}

可以看到有滑,先實(shí)現(xiàn)了add_little_sign的功能,然后實(shí)現(xiàn)add_big_sign的功能嵌削。

1)上面語(yǔ)法糖的裝飾過(guò)程等價(jià)于:

print_info = add_little_sign(print_info)

print_info = add_big_sign(print_info)

裝飾過(guò)程:

原print_info函數(shù) --> 執(zhí)行“print_info = add_little_sign(print_info)” 毛好,使用add_little_sign裝飾原函數(shù)print_ifo --> print_info = wrapper1 --> 執(zhí)行“print_info = add_big_sign(print_info)” ,使用add_big_sign裝飾函數(shù)print_ifo(此時(shí)函數(shù)print_info已是函數(shù)wrapper1) --> print_info = wrapper2

當(dāng)裝飾完畢后苛秕,原print_info已經(jīng)變身成了wrapper2肌访,并且此時(shí)wrapper2中儲(chǔ)存著傳入的wrapper1。那么再調(diào)用此時(shí)的print_info艇劫,執(zhí)行過(guò)程為:調(diào)用裝飾后的print_info --> 調(diào)用wrapper2 --> wrapper2內(nèi)部調(diào)用wrapper1 --> 執(zhí)行wrapper1 --> 裝飾“()” --> 再裝飾“{}” --> 完畢吼驶。

2)使用多個(gè)裝飾器時(shí)功能特性

(1)一般使用語(yǔ)法糖方式裝飾;

(2)會(huì)先用內(nèi)層的裝飾店煞,再用外層的裝飾蟹演;

多個(gè)裝飾器同時(shí)使用時(shí),相當(dāng)于先將內(nèi)層的裝飾器包裹住原函數(shù)顷蟀,再在外面用外層的裝飾器包裹酒请。

四、帶參數(shù)的裝飾器

有時(shí)裝飾器也需要攜帶參數(shù)鸣个,以實(shí)現(xiàn)對(duì)要添加功能的處理羞反。

錯(cuò)誤的裝飾器攜帶參數(shù)的格式:

def decorator(func, a):

? ? def wrapper(*args, **kwargs):

? ? ? ? 要添加的額外功能

? ? ? ? (對(duì)參數(shù)a進(jìn)行操作)

? ? ? ? return func(*args, **kwargs)

? ? return wrapper

@decorator(2)? ? ? # 假如將參數(shù)a設(shè)置為2

def func():

? ? 代碼塊...


>>> 運(yùn)行結(jié)果:

TypeError: 'int' object is not callable? ?

因?yàn)檠b飾器只能接收一個(gè)參數(shù),并且該參數(shù)必須是可調(diào)用的函數(shù)對(duì)象囤萤。

正確的裝飾器攜帶參數(shù)的格式:

def create_decorator(a):

? ? def decorator(func):

? ? ? ? def wrapper(*args, **kwargs):

? ? ? ? ? ? 要添加的額外功能

? ? ? ? ? ? (對(duì)參數(shù)a進(jìn)行操作)

? ? ? ? ? ? return func(*args, **kwargs)

? ? ? ? return wrapper

? ? return decorator

@create_decorator(2)? ? ? # 假如將參數(shù)a設(shè)置為2

def func():

? ? 代碼塊...


>>> 運(yùn)行結(jié)果:

會(huì)正常運(yùn)行昼窗,不會(huì)再拋出異常? ?

上述代碼塊,定義了一個(gè)返回裝飾器的閉包涛舍,通過(guò)閉包能夠存儲(chǔ)外層函數(shù)變量的特性保存住外層函數(shù)的參數(shù)澄惊,然后在裝飾器中進(jìn)行處理。

裝飾過(guò)程等價(jià)于:

decorator = create_decorator(2)? ? # 傳入 參數(shù)2做盅,被儲(chǔ)存至返回的裝飾器內(nèi)

func = decorator(func)? ? ? # 這步開(kāi)始才是真正的裝飾器裝飾過(guò)程

...

五缤削、類裝飾器

裝飾器還有一種特殊的定義方法,就是通過(guò)定義一個(gè)類來(lái)定義裝飾器吹榴。

class Decorator(object):

? ? def __init__(self, func):

? ? ? ? # 初始化操作在此完成

? ? ? ? self.__func = func

? ? # 實(shí)現(xiàn)__call__方法,表示對(duì)象是一個(gè)可調(diào)用對(duì)象滚婉,可以像調(diào)用函數(shù)一樣進(jìn)行調(diào)用图筹。

? ? # 重寫(xiě)類的可調(diào)用魔法方法

? ? def __call__(self, *args, **kwargs):

? ? ? ? # 添加裝飾功能

? ? ? ? 要添加的額外功能

? ? ? ? self.__func()

@Decorator

def comment():

? ? print("發(fā)表評(píng)論")

comment()

說(shuō)明:

在定義類的初始化方法時(shí),定義一個(gè)私有實(shí)例屬性,此屬性是用來(lái)接收要裝飾的函數(shù)對(duì)象远剩;然后重寫(xiě)類的__call__方法扣溺,使對(duì)象被調(diào)用時(shí)實(shí)現(xiàn)原函數(shù)功能的基礎(chǔ)上增加新功能。(擁有__call__方法能夠使對(duì)象被調(diào)用瓜晤,“對(duì)象()”會(huì)自動(dòng)執(zhí)行對(duì)象的__call__方法锥余。)

上述裝飾器裝飾過(guò)程等價(jià)于:

comment = Decorator(comment)? ? # 傳入comment進(jìn)行實(shí)例化對(duì)象

comment()? ? ? # 此步將執(zhí)行類的實(shí)例方法__call__,從而實(shí)現(xiàn)了增加的功能

傳入原函數(shù)comment痢掠,Decorator接收并實(shí)例化出一個(gè)對(duì)象 --> comment = 實(shí)例化的對(duì)象 --> comment() --> 執(zhí)行實(shí)例對(duì)象的__call__方法 --> 完成裝飾驱犹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市足画,隨后出現(xiàn)的幾起案子雄驹,更是在濱河造成了極大的恐慌,老刑警劉巖淹辞,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件医舆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡象缀,警方通過(guò)查閱死者的電腦和手機(jī)蔬将,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)央星,“玉大人娃胆,你說(shuō)我怎么就攤上這事〉嚷” “怎么了里烦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)禁谦。 經(jīng)常有香客問(wèn)我胁黑,道長(zhǎng),這世上最難降的妖魔是什么州泊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任丧蘸,我火速辦了婚禮,結(jié)果婚禮上遥皂,老公的妹妹穿的比我還像新娘力喷。我一直安慰自己,他們只是感情好演训,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布弟孟。 她就那樣靜靜地躺著,像睡著了一般样悟。 火紅的嫁衣襯著肌膚如雪拂募。 梳的紋絲不亂的頭發(fā)上庭猩,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音陈症,去河邊找鬼蔼水。 笑死,一個(gè)胖子當(dāng)著我的面吹牛录肯,可吹牛的內(nèi)容都是我干的趴腋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼论咏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼优炬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起潘靖,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤穿剖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后卦溢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體糊余,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年单寂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贬芥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宣决,死狀恐怖蘸劈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尊沸,我是刑警寧澤威沫,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站洼专,受9級(jí)特大地震影響棒掠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屁商,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一烟很、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡镶,春花似錦雾袱、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至妻枕,卻和暖如春僻族,著一層夾襖步出監(jiān)牢的瞬間粘驰,已是汗流浹背屡谐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工述么, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愕掏。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓度秘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饵撑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子剑梳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359