Python函數(shù)裝飾器原理與用法

裝飾器本質(zhì)上是一個(gè)函數(shù),該函數(shù)用來(lái)處理其他函數(shù),它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能吕漂,裝飾器的返回值也是一個(gè)函數(shù)對(duì)象。它經(jīng)常用于有切面需求的場(chǎng)景尘应,比如:插入日志惶凝、性能測(cè)試、事務(wù)處理犬钢、緩存苍鲜、權(quán)限校驗(yàn)等應(yīng)用場(chǎng)景。裝飾器是解決這類(lèi)問(wèn)題的絕佳設(shè)計(jì)玷犹,有了裝飾器混滔,我們就可以抽離出大量與函數(shù)功能本身無(wú)關(guān)的雷同代碼并繼續(xù)重用。概括的講歹颓,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能坯屿。

嚴(yán)格來(lái)說(shuō),裝飾器只是語(yǔ)法糖巍扛,裝飾器是可調(diào)用的對(duì)象领跛,可以像常規(guī)的可調(diào)用對(duì)象那樣調(diào)用,特殊的地方是裝飾器的參數(shù)是一個(gè)函數(shù)

現(xiàn)在有一個(gè)新的需求撤奸,希望可以記錄下函數(shù)的執(zhí)行時(shí)間隔节,于是在代碼中添加日志代碼:

import time

#遵守開(kāi)放封閉原則

def foo():

start = time.time()

# print(start) # 1504698634.0291758從1970年1月1號(hào)到現(xiàn)在的秒數(shù),那年Unix誕生

time.sleep(3)

end = time.time()

print('spend %s'%(end - start))

foo()

bar()寂呛、bar2()也有類(lèi)似的需求怎诫,怎么做?再在bar函數(shù)里調(diào)用時(shí)間函數(shù)贷痪?這樣就造成大量雷同的代碼幻妓,為了減少重復(fù)寫(xiě)代碼,我們可以這樣做劫拢,重新定義一個(gè)函數(shù):專(zhuān)門(mén)設(shè)定時(shí)間:

import time

def show_time(func):

start_time=time.time()

func()

end_time=time.time()

print('spend %s'%(end_time-start_time))

def foo():

print('hello foo')

time.sleep(3)

show_time(foo)

但是這樣的話(huà),你基礎(chǔ)平臺(tái)的函數(shù)修改了名字肉津,容易被業(yè)務(wù)線(xiàn)的人投訴的,因?yàn)槲覀兠看味家獙⒁粋€(gè)函數(shù)作為參數(shù)傳遞給show_time函數(shù)舱沧。而且這種方式已經(jīng)破壞了原有的代碼邏輯結(jié)構(gòu)妹沙,之前執(zhí)行業(yè)務(wù)邏輯時(shí),執(zhí)行運(yùn)行foo()熟吏,但是現(xiàn)在不得不改成show_time(foo)距糖。那么有沒(méi)有更好的方式的呢玄窝?當(dāng)然有,答案就是裝飾器悍引。

def show_time(f):

def inner():

start = time.time()

f()

end = time.time()

print('spend %s'%(end - start))

return inner

@show_time #foo=show_time(f)

def foo():

print('foo...')

time.sleep(1)

foo()

def bar():

print('bar...')

time.sleep(2)

bar()

輸出結(jié)果:

foo...

spend 1.0005607604980469

bar...

函數(shù)show_time就是裝飾器恩脂,它把真正的業(yè)務(wù)方法f包裹在函數(shù)里面,看起來(lái)像foo被上下時(shí)間函數(shù)裝飾了趣斤。在這個(gè)例子中俩块,函數(shù)進(jìn)入和退出時(shí) ,被稱(chēng)為一個(gè)橫切面(Aspect)浓领,這種編程方式被稱(chēng)為面向切面的編程(Aspect-Oriented Programming)玉凯。

@符號(hào)是裝飾器的語(yǔ)法糖,在定義函數(shù)的時(shí)候使用联贩,避免再一次賦值操作

裝飾器在Python使用如此方便都要?dú)w因于Python的函數(shù)能像普通的對(duì)象一樣能作為參數(shù)傳遞給其他函數(shù)壮啊,可以被賦值給其他變量,可以作為返回值撑蒜,可以被定義在另外一個(gè)函數(shù)內(nèi)歹啼。

裝飾器有2個(gè)特性,一是可以把被裝飾的函數(shù)替換成其他函數(shù)座菠, 二是可以在加載模塊時(shí)候立即執(zhí)行

def decorate(func):

print('running decorate', func)

def decorate_inner():

print('running decorate_inner function')

return func()

return decorate_inner

@decorate

def func_1():

print('running func_1')

if __name__ == '__main__':

print(func_1)

#running decorate <function func_1 at 0x000001904743DEA0>

# <function decorate.<locals>.decorate_inner at 0x000001904743DF28>

func_1()

#running decorate_inner function

# running func_1

通過(guò)args 和 *kwargs 傳遞被修飾函數(shù)中的參數(shù)

def decorate(func):

def decorate_inner(*args, **kwargs):

print(type(args), type(kwargs))

print('args', args, 'kwargs', kwargs)

return func(*args, **kwargs)

return decorate_inner

@decorate

def func_1(*args, **kwargs):

print(args, kwargs)

if __name__ == '__main__':

func_1('1', '2', '3', para_1='1', para_2='2', para_3='3')

#返回結(jié)果

#<class 'tuple'> <class 'dict'>

# args ('1', '2', '3') kwargs {'para_1': '1', 'para_2': '2', 'para_3': '3'}

# ('1', '2', '3') {'para_1': '1', 'para_2': '2', 'para_3': '3'}

帶參數(shù)的被裝飾函數(shù) 

import time

# 定長(zhǎng)

def show_time(f):

def inner(x,y):

start = time.time()

f(x,y)

end = time.time()

print('spend %s'%(end - start))

return inner

@show_time

def add(a,b):

print(a+b)

time.sleep(1)

add(1,2)

不定長(zhǎng)

import time

#不定長(zhǎng)

def show_time(f):

def inner(*x,**y):

start = time.time()

f(*x,**y)

end = time.time()

print('spend %s'%(end - start))

return inner

@show_time

def add(*a,**b):

sum=0

for i in a:

sum+=i

print(sum)

time.sleep(1)

add(1,2,3,4)

帶參數(shù)的裝飾器

在上面的裝飾器調(diào)用中狸眼,比如@show_time,該裝飾器唯一的參數(shù)就是執(zhí)行業(yè)務(wù)的函數(shù)浴滴。裝飾器的語(yǔ)法允許我們?cè)谡{(diào)用時(shí)拓萌,提供其它參數(shù),比如@decorator(a)升略。這樣微王,就為裝飾器的編寫(xiě)和使用提供了更大的靈活性。

import time

def time_logger(flag=0):

def show_time(func):

def wrapper(*args, **kwargs):

start_time = time.time()

func(*args, **kwargs)

end_time = time.time()

print('spend %s' % (end_time - start_time))

if flag:

print('將這個(gè)操作的時(shí)間記錄到日志中')

return wrapper

return show_time

@time_logger(flag=1)

def add(*args, **kwargs):

time.sleep(1)

sum = 0

for i in args:

sum += i

print(sum)

add(1, 2, 5)

@time_logger(flag=1) 做了兩件事:

(1)time_logger(1):得到閉包函數(shù)show_time品嚣,里面保存環(huán)境變量flag

(2)@show_time?? :add=show_time(add)

上面的time_logger是允許帶參數(shù)的裝飾器炕倘。它實(shí)際上是對(duì)原有裝飾器的一個(gè)函數(shù)封裝,并返回一個(gè)裝飾器(一個(gè)含有參數(shù)的閉包函數(shù))翰撑。當(dāng)我 們使用@time_logger(1)調(diào)用的時(shí)候罩旋,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中眶诈。

疊放裝飾器

執(zhí)行順序是什么

如果一個(gè)函數(shù)被多個(gè)裝飾器修飾涨醋,其實(shí)應(yīng)該是該函數(shù)先被最里面的裝飾器修飾后(下面例子中函數(shù)main()先被inner裝飾,變成新的函數(shù))逝撬,變成另一個(gè)函數(shù)后浴骂,再次被裝飾器修飾

def outer(func):

print('enter outer', func)

def wrapper():

print('running outer')

func()

return wrapper

def inner(func):

print('enter inner', func)

def wrapper():

print('running inner')

func()

return wrapper

@outer

@inner

def main():

print('running main')

if __name__ == '__main__':

main()

#返回結(jié)果

# enter inner <function main at 0x000001A9F2BCDF28>

# enter outer <function inner.<locals>.wrapper at 0x000001A9F2BD5048>

# running outer

# running inner

# running main

類(lèi)裝飾器

相比函數(shù)裝飾器,類(lèi)裝飾器具有靈活度大宪潮、高內(nèi)聚溯警、封裝性等優(yōu)點(diǎn)趣苏。使用類(lèi)裝飾器還可以依靠類(lèi)內(nèi)部的__call__方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí)愧膀,就會(huì)調(diào)用此方法。

import time

class Foo(object):

def __init__(self, func):

self._func = func

def __call__(self):

start_time=time.time()

self._func()

end_time=time.time()

print('spend %s'%(end_time-start_time))

@Foo #bar=Foo(bar)

def bar():

print ('bar')

time.sleep(2)

bar()? #bar=Foo(bar)()>>>>>>>沒(méi)有嵌套關(guān)系了,直接active Foo的 __call__方法

標(biāo)準(zhǔn)庫(kù)中有多種裝飾器

例如:裝飾方法的函數(shù)有property, classmethod, staticmethod谣光; functools模塊中的lru_cache, singledispatch,? wraps 等等

from functools import lru_cache

from functools import singledispatch

from functools import wraps

functools.wraps使用裝飾器極大地復(fù)用了代碼檩淋,但是他有一個(gè)缺點(diǎn)就是原函數(shù)的元信息不見(jiàn)了,比如函數(shù)的docstring萄金、__name__蟀悦、參數(shù)列表,先看例子:

def foo():

print("hello foo")

print(foo.__name__)# foo

def logged(func):

def wrapper(*args, **kwargs):

print (func.__name__ + " was called")

return func(*args, **kwargs)

return wrapper

@logged

def cal(x):

resul=x + x * x

print(resul)

cal(2)

#6

#cal was called

print(cal.__name__)# wrapper

print(cal.__doc__)#None

#函數(shù)f被wrapper取代了氧敢,當(dāng)然它的docstring日戈,__name__就是變成了wrapper函數(shù)的信息了。

好在我們有functools.wraps孙乖,wraps本身也是一個(gè)裝飾器浙炼,它能把原函數(shù)的元信息拷貝到裝飾器函數(shù)中,這使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息了唯袄。

from functools import wraps

def logged(func):

@wraps(func)

def wrapper(*args, **kwargs):

print(func.__name__ + " was called")

return func(*args, **kwargs)

return wrapper

@logged

def cal(x):

return x + x * x

print(cal.__name__) # cal

使用裝飾器會(huì)產(chǎn)生我們可能不希望出現(xiàn)的副作用弯屈, 例如:改變被修飾函數(shù)名稱(chēng),對(duì)于調(diào)試器或者對(duì)象序列化器等需要使用內(nèi)省機(jī)制的那些工具恋拷,可能會(huì)無(wú)法正常運(yùn)行资厉;

其實(shí)調(diào)用裝飾器后,會(huì)將同一個(gè)作用域中原來(lái)函數(shù)同名的那個(gè)變量(例如下面的func_1),重新賦值為裝飾器返回的對(duì)象蔬顾;使用@wraps后宴偿,會(huì)把與內(nèi)部函數(shù)(被修飾函數(shù),例如下面的func_1)相關(guān)的重要元數(shù)據(jù)全部復(fù)制到外圍函數(shù)(例如下面的decorate_inner)

from functools import wraps

def decorate(func):

print('running decorate', func)

@wraps(func)

def decorate_inner():

print('running decorate_inner function', decorate_inner)

return func()

return decorate_inner

@decorate

def func_1():

print('running func_1', func_1)

if __name__ == '__main__':

func_1()

#輸出結(jié)果

#running decorate <function func_1 at 0x0000023E8DBD78C8>

# running decorate_inner function <function func_1 at 0x0000023E8DBD7950>

# running func_1 <function func_1 at 0x0000023E8DBD7950>

文章同步發(fā)布:?https://www.geek-share.com/detail/2784034826.html

參考文章:

Java經(jīng)典設(shè)計(jì)模式之策略模式原理與用法詳解

Python設(shè)計(jì)模式之抽象工廠模式原理與用法詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诀豁,一起剝皮案震驚了整個(gè)濱河市窄刘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舷胜,老刑警劉巖都哭,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逞带,居然都是意外死亡欺矫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)展氓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)穆趴,“玉大人,你說(shuō)我怎么就攤上這事遇汞∥疵茫” “怎么了簿废?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)络它。 經(jīng)常有香客問(wèn)我族檬,道長(zhǎng),這世上最難降的妖魔是什么化戳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任单料,我火速辦了婚禮,結(jié)果婚禮上点楼,老公的妹妹穿的比我還像新娘扫尖。我一直安慰自己,他們只是感情好掠廓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布换怖。 她就那樣靜靜地躺著,像睡著了一般蟀瞧。 火紅的嫁衣襯著肌膚如雪沉颂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天悦污,我揣著相機(jī)與錄音兆览,去河邊找鬼。 笑死塞关,一個(gè)胖子當(dāng)著我的面吹牛抬探,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帆赢,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼小压,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了椰于?” 一聲冷哼從身側(cè)響起怠益,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘾婿,沒(méi)想到半個(gè)月后蜻牢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偏陪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年抢呆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笛谦。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抱虐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饥脑,到底是詐尸還是另有隱情恳邀,我是刑警寧澤懦冰,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谣沸,受9級(jí)特大地震影響刷钢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乳附,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一内地、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧许溅,春花似錦瓤鼻、人聲如沸秉版。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)清焕。三九已至并蝗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秸妥,已是汗流浹背滚停。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粥惧,地道東北人键畴。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像突雪,于是被迫代替她去往敵國(guó)和親起惕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 裝飾器函數(shù) 楔子 作為一個(gè)會(huì)寫(xiě)函數(shù)的python開(kāi)發(fā),我們從今天開(kāi)始要去公司上班了督函。寫(xiě)了一個(gè)函數(shù)嘀粱,就交給其他開(kāi)發(fā)用...
    go以恒閱讀 272評(píng)論 0 0
  • 每個(gè)人都有的內(nèi)褲主要功能是用來(lái)遮羞,但是到了冬天它沒(méi)法為我們防風(fēng)御寒辰狡,咋辦锋叨?我們想到的一個(gè)辦法就是把內(nèi)褲改造一下,...
    chen_000閱讀 1,362評(píng)論 0 3
  • Python裝飾器的高級(jí)用法(翻譯) 原文地址https://www.codementor.io/python/t...
    城南道閱讀 4,812評(píng)論 1 22
  • 在學(xué)習(xí)Python的過(guò)程中宛篇,我相信有很多人和我一樣悲柱,對(duì)Python的裝飾器一直覺(jué)得很困惑,我也是困惑了好久些己,并通過(guò)...
    愚灬墨閱讀 460評(píng)論 1 1
  • ??成人與成人之間都會(huì)因?yàn)閮r(jià)值觀不同而產(chǎn)生無(wú)法交流的時(shí)候豌鸡,那么成人與孩子之間呢嘿般,我們又該如何溝通? ??今天想分享...
    欣瑜Sherry閱讀 1,480評(píng)論 0 1