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)景用多多使用担扑。