一篇文章带你从认识Python装饰器到熟练使用
一.簡單裝飾器
裝飾器其實(shí)就是一個(gè)以函數(shù)作為參數(shù)并返回一個(gè)替換函數(shù)的可執(zhí)行函數(shù)。本質(zhì)上就是一個(gè)函數(shù),該函數(shù)用來處理其他函數(shù),它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能,裝飾器的返回值也是一個(gè)函數(shù)對象,它經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務(wù)處理、緩存、權(quán)限校驗(yàn)等應(yīng)用場景。裝飾器是解決這類問題的絕佳設(shè)計(jì),有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。
概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。
從一個(gè)例子說起:
業(yè)務(wù)生產(chǎn)中大量調(diào)用的函數(shù)::
def foo():print('hello foo') foo()現(xiàn)在有一個(gè)新的需求,希望可以記錄下函數(shù)的執(zhí)行時(shí)間,于是在代碼中添加日志代碼:
import time def foo():start_time=time.time()print('hello foo')time.sleep(3)end_time=time.time()print('spend %s'%(end_time-start_time))foo()bar()、bar2()也有類似的需求,怎么做?再在bar函數(shù)里調(diào)用時(shí)間函數(shù)?這樣就造成大量雷同的代碼,為了減少重復(fù)寫代碼,我們可以這樣做,重新定義一個(gè)函數(shù):專門設(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)邏輯上不難理解,而且運(yùn)行正常。 但是這樣的話,你基礎(chǔ)平臺的函數(shù)修改了名字,容易被業(yè)務(wù)線的人投訴的,因?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)。那么有沒有更好的方式的呢?當(dāng)然有,答案就是裝飾器。
if foo()==show_time(foo) :問題解決!所以,我們需要show_time(foo)返回一個(gè)函數(shù)對象,而這個(gè)函數(shù)對象內(nèi)則是核心業(yè)務(wù)函數(shù):執(zhí)行func()與裝飾函數(shù)時(shí)間計(jì)算,修改如下:
import timedef show_time(func):def wrapper():start_time=time.time()func()end_time=time.time()print('spend %s'%(end_time-start_time))return wrapperdef foo():print('hello foo')time.sleep(3)foo=show_time(foo) foo()函數(shù)show_time就是裝飾器,它把真正的業(yè)務(wù)方法func包裹在函數(shù)里面,看起來像foo被上下時(shí)間函數(shù)裝飾了。在這個(gè)例子中,函數(shù)進(jìn)入和退出時(shí) ,被稱為一個(gè)橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
@符號是裝飾器的語法糖,在定義函數(shù)的時(shí)候使用,避免再一次賦值操作
import timedef show_time(func):def wrapper():start_time=time.time()func()end_time=time.time()print('spend %s'%(end_time-start_time))return wrapper@show_time #foo=show_time(foo) def foo():print('hello foo')time.sleep(3)@show_time #bar=show_time(bar) def bar():print('in the bar')time.sleep(2)foo() print('***********') bar()如上所示,這樣我們就可以省去bar = show_time(bar)這一句了,直接調(diào)用bar()即可得到想要的結(jié)果。如果我們有其他的類似函數(shù),我們可以繼續(xù)調(diào)用裝飾器來修飾函數(shù),而不用重復(fù)修改函數(shù)或者增加新的封裝。這樣,我們就提高了程序的可重復(fù)利用性,并增加了程序的可讀性。
這里需要注意的問題:
foo=show_time(foo)其實(shí)是把wrapper引用的對象引用給了foo,而wrapper里的變量func之所以可以用,就是因?yàn)閣rapper是一個(gè)閉包函數(shù)。
二.帶參數(shù)的被裝飾函數(shù)
import timedef show_time(func):def wrapper(a,b):start_time=time.time()func(a,b)end_time=time.time()print('spend %s'%(end_time-start_time))return wrapper@show_time #add=show_time(add) def add(a,b):time.sleep(1)print(a+b)add(2,4) import timedef show_time(func):def wrapper(a,b):start_time=time.time()ret=func(a,b)end_time=time.time()print('spend %s'%(end_time-start_time))return retreturn wrapper@show_time #add=show_time(add) def add(a,b):time.sleep(1)return a+bprint(add(2,5))不定長參數(shù)
import timedef 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))return wrapper@show_time #add=show_time(add) def add(*args,**kwargs):time.sleep(1)sum=0for i in args:sum+=iprint(sum)add(2,4,8,9)三.帶參數(shù)的裝飾器
裝飾器還有更大的靈活性,例如帶參數(shù)的裝飾器:在上面的裝飾器調(diào)用中,比如@show_time,該裝飾器唯一的參數(shù)就是執(zhí)行業(yè)務(wù)的函數(shù)。裝飾器的語法允許我們在調(diào)用時(shí),提供其它參數(shù),比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
import timedef 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 wrapperreturn show_time@time_logger(3) def add(*args,**kwargs):time.sleep(1)sum=0for i in args:sum+=iprint(sum)add(2,7,5)@time_logger(3)做了兩件事:
(1)ime_logger(3):得到閉包函數(shù)show_time,里面保存環(huán)境變量flag
(2)@show_time :add=show_time(add)
上面的time_logger是允許帶參數(shù)的裝飾器。它實(shí)際上是對原有裝飾器的一個(gè)函數(shù)封裝,并返回一個(gè)裝飾器(一個(gè)含有參數(shù)的閉包函數(shù))。當(dāng)我 們使用@time_logger(3)調(diào)用的時(shí)候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。
四,多層裝飾器
def makebold(fn):def wrapper():return "<b>" + fn() + "</b>"return wrapperdef makeitalic(fn):def wrapper():return "<i>" + fn() + "</i>"return wrapper@makebold @makeitalic def hello():return "hello alvin"hello()過程:
五.類裝飾器
再來看看類裝飾器,相比函數(shù)裝飾器,類裝飾器具有靈活度大、高內(nèi)聚、封裝性等優(yōu)點(diǎn)。使用類裝飾器還可以依靠類內(nèi)部的__call__方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí),就會調(diào)用此方法。
''' 學(xué)習(xí)中遇到問題沒人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:725638078 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' import timeclass Foo(object):def __init__(self, func):self._func = funcdef __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)()>>>>>>>沒有嵌套關(guān)系了,直接active Foo的 __call__方法六.functools.wraps
使用裝飾器極大地復(fù)用了代碼,但是他有一個(gè)缺點(diǎn)就是原函數(shù)的元信息不見了,比如函數(shù)的docstring、__name__、參數(shù)列表,先看例子:
def foo():print("hello foo")print(foo.__name__) #####################def logged(func):def wrapper(*args, **kwargs):print (func.__name__ + " was called")return func(*args, **kwargs)return wrapper@logged def cal(x):return x + x * xprint(cal.__name__)######## # foo # wrapper解釋:
@logged def f(x):return x + x * x等價(jià)于:
def f(x):return x + x * x f = logged(f)不難發(fā)現(xiàn),函數(shù)f被wrapper取代了,當(dāng)然它的docstring,__name__就是變成了wrapper函數(shù)的信息了。
print (f.__name__ ) # prints 'wrapper' print (f.__doc__ ) # prints None這個(gè)問題就比較嚴(yán)重的,好在我們有functools.wraps,wraps本身也是一個(gè)裝飾器,它能把原函數(shù)的元信息拷貝到裝飾器函數(shù)中,這使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息了。
from functools import wrapsdef 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 * xprint(cal.__name__) #cal結(jié)尾給大家推薦一個(gè)非常好的學(xué)習(xí)教程,希望對你學(xué)習(xí)Python有幫助!
Python基礎(chǔ)入門教程推薦:←點(diǎn)擊左邊藍(lán)色文字就可以跳轉(zhuǎn)觀看了
Python爬蟲案例教程推薦:←點(diǎn)擊左邊藍(lán)色文字就可以跳轉(zhuǎn)觀看了
總結(jié)
以上是生活随笔為你收集整理的一篇文章带你从认识Python装饰器到熟练使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 整理了7道Python函数的练习题,希望
- 下一篇: 一文搞懂 Python 的 import