Python知識(shí)點(diǎn):理解和使用裝飾器 @decorator

Python的裝飾器(decorator)是一個(gè)很棒的機(jī)制臣镣,也是熟練運(yùn)用Python的必殺技之一辅愿。裝飾器智亮,顧名思義,就是用來裝飾的点待,它裝飾的是一個(gè)函數(shù)阔蛉,保持被裝飾函數(shù)的原有功能,再裝飾上(添油加醋)一些其它功能癞埠,并返回帶有新增功能的函數(shù)對(duì)象状原,所以裝飾器本質(zhì)上是一個(gè)返回函數(shù)對(duì)象的函數(shù)(確切的說,裝飾器應(yīng)該是可調(diào)用對(duì)象苗踪,除了函數(shù)颠区,類也可以作為裝飾器)。

在編程過程中通铲,我們經(jīng)常遇到這樣的場(chǎng)景:登錄校驗(yàn)毕莱,權(quán)限校驗(yàn),日志記錄等颅夺,這些功能代碼在各個(gè)環(huán)節(jié)都可能需要朋截,但又十分雷同,通過裝飾器來抽象吧黄、剝離這部分代碼可以很好解決這類場(chǎng)景质和。

學(xué)習(xí)Python中的小伙伴,需要學(xué)習(xí)資料的話稚字,可以前往我的微信公眾號(hào):速學(xué)Python饲宿,后臺(tái)回復(fù):簡(jiǎn)書,即可拿Python學(xué)習(xí)資料

這里有我自己整理了一套最新的python系統(tǒng)學(xué)習(xí)教程胆描,包括從基礎(chǔ)的python腳本到web開發(fā)瘫想、爬蟲、數(shù)據(jù)分析昌讲、數(shù)據(jù)可視化国夜、機(jī)器學(xué)習(xí)等。送給正在學(xué)習(xí)python的小伙伴短绸!這里是python學(xué)習(xí)者聚集地车吹,歡迎初學(xué)和進(jìn)階中的小伙伴!

裝飾器是什么醋闭?

要理解Python的裝飾器窄驹,首先我們先理解一下Python的函數(shù)對(duì)象。我們知道证逻,在Python里一切都是對(duì)象乐埠,函數(shù)也不例外,函數(shù)是第一類對(duì)象(first-class objects),它可以賦值給變量丈咐,也可以作為list的元素瑞眼,還可以作為參數(shù)傳遞給其它函數(shù)。

函數(shù)可以被變量引用

定義一個(gè)簡(jiǎn)單的函數(shù):

defsay_hi():print('Hi!')say_hi()# Output: Hi!

我們可以通過另外一個(gè)變量say_hi2來引用say_hi函數(shù):

say_hi2 = say_hiprint(say_hi2)# Output: <function say_hi at 0x7fed671c4378>say_hi2()# Output: Hi!

上面的語句中say_hi2 和 say_hi 指向了同樣的函數(shù)定義棵逊,二者的執(zhí)行結(jié)果也相同伤疙。

函數(shù)可以作為參數(shù)傳遞給其它函數(shù)

defsay_more(say_hi_func):print('More')? ? say_hi_func()say_more(say_hi)# Output:#? ? More#? ? Hi

在上面的例子中,我們把say_hi函數(shù)當(dāng)做參數(shù)傳遞給say_more函數(shù)辆影,say_hi 被變量 say_hi_func 引用徒像。

函數(shù)可以定義在其它函數(shù)內(nèi)部

defsay_hi():print('Hi!')defsay_name():print('Tom')? ? say_name()say_hi()# Output:#? ? Hi!#? ? Tomsay_name()# 報(bào)錯(cuò)

上述代碼中,我們?cè)趕ay_hi()函數(shù)內(nèi)部定義了另外一個(gè)函數(shù)say_name()秸歧。say_name()只在say_hi函數(shù)內(nèi)部可見(即厨姚,它的作用域在say_hi函數(shù)內(nèi)部)衅澈,在say_hi外包調(diào)用時(shí)就會(huì)出錯(cuò)键菱。

函數(shù)可以返回其它函數(shù)的引用

defsay_hi():print('Hi!')defsay_name():print('Tom')returnsay_namesay_name_func = say_hi()# 打印Hi!,并返回say_name函數(shù)對(duì)象# 并賦值給say_name_funcsay_name_func()# 打印 Tom

上面的例子今布,say_hi函數(shù)返回了其內(nèi)部定義的函數(shù)say_name的引用经备。這樣在say_hi函數(shù)外部也可以使用say_name函數(shù)了。

前面我們理解了函數(shù)部默,這有助于我們接下來弄明白裝飾器侵蒙。

裝飾器(Decorator)

裝飾器是可調(diào)用對(duì)象(callable objects),它用來修改函數(shù)或類傅蹂。

可調(diào)用對(duì)象就是可以接受某些參數(shù)并返回某些對(duì)象的對(duì)象纷闺。Python里的函數(shù)和類都是可調(diào)用對(duì)象。

函數(shù)裝飾器份蝴,就是接受函數(shù)作為參數(shù)犁功,并對(duì)函數(shù)參數(shù)做一些包裝,然后返回增加了包裝的函數(shù)婚夫,即生成了一個(gè)新函數(shù)浸卦。

讓我們看看下面這個(gè)例子:

defdecorator_func(some_func):# define another wrapper function which modifies some_funcdefwrapper_func():print("Wrapper function started")? ? ? ? some_func()? ? ? ? print("Wrapper function ended")returnwrapper_func# Wrapper function add something to the passed function and decorator returns the wrapper functiondefsay_hello():print("Hello")? say_hello = decorator_func(say_hello)say_hello()# Output:#? Wrapper function started#? Hello#? Wrapper function ended

上面例子中,decorator_func 就是定義的裝飾器函數(shù)案糙,它接受some_func作為參數(shù)限嫌。它定義了一個(gè)wrapper_func函數(shù),該函數(shù)調(diào)用了some_func但也增加了一些自己的代碼时捌。

上面代碼中使用裝飾器的方法看起來有點(diǎn)復(fù)雜怒医,其實(shí)真正的裝飾器的Python語法是這樣的:

裝飾器的Python語法

@decorator_funcdefsay_hi():print'Hi!'

@ 符合是裝飾器的語法糖,在定義函數(shù)say_hi時(shí)使用奢讨,避免了再一次的賦值語句裆熙。

上面的語句等同于:

defsay_hi():print'Hi!'say_hi = decorator_func(say_hi)

裝飾器的順序

@a@b@cdeffoo():print('foo')# 等同于:foo = a(b(c(foo)))

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

defdecorator_func(some_func):defwrapper_func(*args, **kwargs):print("Wrapper function started")? ? ? ? some_func(*args, **kwargs)? ? ? ? print("Wrapper function ended")returnwrapper_func@decorator_func? ? defsay_hi(name):print("Hi!"+ name)

上面代碼中,say_hi函數(shù)帶有一個(gè)參數(shù)。通常情況下入录,不同功能的函數(shù)可以有不同類別蛤奥、不同數(shù)量的參數(shù),在寫wrapper_func的時(shí)候僚稿,我們不確定參數(shù)的名稱和數(shù)量凡桥,可以通過*args 和 **kwargs 來引用函數(shù)參數(shù)。

帶參數(shù)的裝飾器

不僅被裝飾的函數(shù)可以帶參數(shù)蚀同,裝飾器本身也可以帶參數(shù)缅刽。參考下面的例子:

defuse_logging(level):defdecorator(func):defwrapper(*args, **kwargs):iflevel =="warn":? ? ? ? ? ? ? ? logging.warn("%s is running"% func.__name__)returnfunc(*args)returnwrapperreturndecorator@use_logging(level="warn")deffoo(name='foo'):print("i am %s"% name)

簡(jiǎn)單來說,帶參數(shù)的裝飾器就是在沒有參數(shù)的裝飾器外面再嵌套一個(gè)參數(shù)的函數(shù)蠢络,該函數(shù)返回那個(gè)無參數(shù)裝飾器即可衰猛。

作為裝飾器

前面我們提到裝飾器是可調(diào)用對(duì)象。在Python里面刹孔,除了函數(shù)啡省,類也是可調(diào)用對(duì)象。使用類裝飾器髓霞,優(yōu)點(diǎn)是靈活性大卦睹,高內(nèi)聚,封裝性方库。通過實(shí)現(xiàn)類內(nèi)部的__call__方法结序,當(dāng)使用 @ 語法糖把裝飾器附加到函數(shù)上時(shí),就會(huì)調(diào)用此方法纵潦。

classFoo(object):def__init__(self, func):self._func = funcdef__call__(self):print('class decorator runing')? ? self._func()print('class decorator ending')@Foodefsay_hi():print('Hi!')say_hi()# Output:# class decorator running# Hi!# class decorator ending

functools.wraps

使用裝飾器極大地復(fù)用了代碼徐鹤,但是他有一個(gè)缺點(diǎn)就是原函數(shù)的元信息不見了,比如函數(shù)的docstring邀层、__name__返敬、參數(shù)列表,先看看下面例子:

defdecorator_func(some_func):defwrapper_func(*args, **kwargs):print("Wrapper function started")? ? ? ? some_func(*args, **kwargs)? ? ? ? print("Wrapper function ended")returnwrapper_func@decorator_func? ? defsay_hi(name):'''Say hi to somebody'''print("Hi!"+ name)print(say_hi.__name__)# Output: wrapper_funcprint(say_hi.__doc__)# Output: None

可以看到被济,say_hi函數(shù)被wrapper_func函數(shù)取代救赐,它的__name__ 和 docstring 也自然是wrapper_func函數(shù)的了。

不過不用擔(dān)心只磷,Python有functools.wraps经磅,wraps本身也是一個(gè)裝飾器,它的作用就是把原函數(shù)的元信息拷貝到裝飾器函數(shù)中钮追,使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息预厌。

fromfunctoolsimportwrapsdefdecorator_func(some_func):? ? @wraps(func)defwrapper_func(*args, **kwargs):print("Wrapper function started")? ? ? ? some_func(*args, **kwargs)? ? ? ? print("Wrapper function ended")returnwrapper_func@decorator_func? ? defsay_hi(name):'''Say hi to somebody'''print("Hi!"+ name)print(say_hi.__name__)# Output: say_hiprint(say_hi.__doc__)# Output: Say hi to somebody

類的內(nèi)置裝飾器

類屬性@property

靜態(tài)方法@staticmethod

類方法@classmethod

通常,我們需要先實(shí)例化一個(gè)類的對(duì)象元媚,再調(diào)用其方法轧叽。

若類的方法使用了@staticmethod或@classmethod苗沧,就可以不需要實(shí)例化,直接類名.方法名()來調(diào)用炭晒。

從使用上來看待逞,@staticmethod不需要指代自身對(duì)象的self或指代自身類的cls參數(shù),就跟使用普通函數(shù)一樣网严。@classmethod不需要self參數(shù)识樱,但第一個(gè)參數(shù)必須是指代自身類的cls參數(shù)。如果在@staticmethod中要調(diào)用到這個(gè)類的一些屬性方法震束,只能直接類名.屬性名怜庸,或類名.方法名的方式。

而@classmethod因?yàn)槌钟衏ls參數(shù)垢村,可以來調(diào)用類的屬性割疾,類的方法,實(shí)例化對(duì)象等嘉栓。

總結(jié)

通過認(rèn)識(shí)Python的函數(shù)宏榕,我們逐步弄清了裝飾器的來龍去脈。裝飾器是代碼復(fù)用的好工具胸懈,在編程過程中可以在適當(dāng)?shù)膱?chǎng)景用多多使用担扑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恰响,一起剝皮案震驚了整個(gè)濱河市趣钱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胚宦,老刑警劉巖首有,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異枢劝,居然都是意外死亡井联,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門您旁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烙常,“玉大人,你說我怎么就攤上這事鹤盒〔显啵” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵侦锯,是天一觀的道長(zhǎng)驼鞭。 經(jīng)常有香客問我,道長(zhǎng)尺碰,這世上最難降的妖魔是什么挣棕? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任译隘,我火速辦了婚禮,結(jié)果婚禮上洛心,老公的妹妹穿的比我還像新娘固耘。我一直安慰自己,他們只是感情好词身,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布玻驻。 她就那樣靜靜地躺著,像睡著了一般偿枕。 火紅的嫁衣襯著肌膚如雪璧瞬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天渐夸,我揣著相機(jī)與錄音嗤锉,去河邊找鬼。 笑死墓塌,一個(gè)胖子當(dāng)著我的面吹牛瘟忱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苫幢,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼访诱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了韩肝?” 一聲冷哼從身側(cè)響起触菜,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哀峻,沒想到半個(gè)月后涡相,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剩蟀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年催蝗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片育特。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丙号,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缰冤,到底是詐尸還是另有隱情犬缨,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布锋谐,位于F島的核電站遍尺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涮拗。R本人自食惡果不足惜乾戏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一迂苛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼓择,春花似錦三幻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至摆出,卻和暖如春朗徊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背偎漫。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工爷恳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人象踊。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓温亲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杯矩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栈虚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 文章首發(fā)于我的技術(shù)博客:你可以在上面看到更多的Python教程和python爬蟲 Python的裝飾器(decor...
    OSC開源社區(qū)閱讀 338評(píng)論 1 9
  • 寫在前面的話 代碼中的# > 表示的是輸出結(jié)果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 2,764評(píng)論 0 8
  • 每個(gè)人都有的內(nèi)褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風(fēng)御寒史隆,咋辦魂务?我們想到的一個(gè)辦法就是把內(nèi)褲改造一下,...
    chen_000閱讀 1,364評(píng)論 0 3
  • 凍疆了的面孔 被風(fēng)吹落 碎了一地 撿起逆害,就像是 被撕的人民幣 小心拼湊头镊,這 唯一的臉面 然后 放到離心最近的地方 ...
    洛水秦韻閱讀 154評(píng)論 0 1
  • 普洱茶手工壓餅制作中
    金龍一劍閱讀 105評(píng)論 0 0