python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上
Python高級編程——裝飾器Decorator超詳細講解(上篇)
送你小心心記得關注我哦!!
進入正文
全文摘要
裝飾器decorator,是python語言的重要特性,我們平時都會遇到,無論是面向對象的設計或者是使用相關的庫,但是很少有文章對裝飾器的來龍去脈進行深入詳解,導致的結果就是很多人知其然,不知其所以然,所以我打算出一期關于Python裝飾器的詳解文章,由于內(nèi)容較多,該文章共分為上下兩篇,本文講解上篇,下篇會在后面發(fā)出,請記得持續(xù)關注哦!文章偏長,閱讀全文約20min。
全文目錄
01 python裝飾器誕生的背景
1.1 從一種簡單的情況說起
1.2 非“裝飾器”實現(xiàn)添加額外功能
02 什么是裝飾器decorator
2.1 裝飾器的兩個層面的定義
2.2 裝飾起的作用——兩個方面
2.3 裝飾器的使用場景
03 裝飾器的實現(xiàn)
3.1 裝飾器的逐步實現(xiàn)
3.2 裝飾器的一般“模板”
04 裝飾器的分類實現(xiàn)(下篇預告)
python裝飾器詳解(上篇)
01
python裝飾器誕生的背景
01 decorator誕生的背景
1.1 從一種簡單的情況說起
裝飾器的定義很是抽象,各個不同的文章有不同的表述,但是我不打算一開始就告訴你什么是Python的裝飾器,這樣顯太過突兀,我們來看一個小例子。
先定義一個簡單的函數(shù):
def myfunc:
print('我是函數(shù)myfunc')
myfunc() #調用函數(shù)
然后呢,我想看看這個函數(shù)執(zhí)行這個函數(shù)用了多長時間,好吧,那么我們可以這樣做:
import time
def myfunc:
start = time.clock()
print('我是函數(shù)myfunc')
end = time.clock()
print(f'函數(shù)所花費的時間為 :{end - start}')
myfunc() #函數(shù)調用
我們現(xiàn)在已經(jīng)達到了我們的目的。但是如果是我們還想繼續(xù)給另外的一些函數(shù)也實現(xiàn)同樣的功能。那我們是不是給每個函數(shù)都添加這么幾句話呢?當然可以,但是不高效,而且很麻煩。如果有某一種方式可以一次性解決所有的問題,那自然最好不過了,于是“裝飾器”就應運而生。
在上面的例子中,函數(shù)本身的功能只是打印一句話而已,但是經(jīng)過改造后的函數(shù)不僅要能夠打印這一句話,還要能夠顯示函數(shù)執(zhí)行所花費的時間,這相當于我要給這個函數(shù)添加額外的功能,注意這個關鍵字,其實“裝飾器”就是專門給函數(shù)添加額外的功能的。
01 decorator誕生的背景
1.2 非“裝飾器”實現(xiàn)添加額外功能
還記得嗎,函數(shù)在Python中是一等公民,那么我們可以考慮重新定義一個函數(shù)timeit,將myfunc的引用傳遞給他,然后在timeit中調用myfunc并進行計時,這樣,我們就達到了不改動myfunc定義但是又添加了額外功能的目的,代碼如下:
import time
def myfunc():
print("我是函數(shù)myfunc")
def timeit(function):
start = time.clock()
function()
end =time.clock()
print(f'函數(shù)執(zhí)行所花費的時間為:{end-start}')
timeit(myfunc)
運行結果為:
我是函數(shù)myfunc
函數(shù)執(zhí)行所花費的時間為:0.0004924657368762765
上面的代碼看起來邏輯上并沒有問題,也達到了我們所要實現(xiàn)的目的!但是,我們雖然沒有修改函數(shù)myfunc定義中的代碼,但是我們似乎修改了調用部分的代碼。原本我們是這樣調用的:myfunc(),修改以后變成了:timeit(myfunc)。這樣的話,如果myfunc在N處都被調用了,你就不得不去修改這N處的代碼。或者更極端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數(shù)是你交給別人使用的。
其實將函數(shù)作為參數(shù)傳遞,已經(jīng)具備了裝飾器的雛形了,但是上面的實現(xiàn)還不夠好,下面會給出更好地實現(xiàn)方式。
02
什么是裝飾器-decorator
02 什么是裝飾器decorator
2.1 裝飾器的定義——兩個層面
一般而言,如果我需要給函數(shù)添加額外的某一些功能,我需要修改函數(shù)的源代碼,但是如前面所說,這樣麻煩,而且不高效,裝飾器就是專門的解決方案!
裝飾器的定義主要從兩個不同層面進行說明的。
在Python里面有兩層定義:
第一:從設計模式的層面上——代碼重用
裝飾器是一個很著名的設計模式,經(jīng)常被用于有切面需求的場景,較為經(jīng)典的應用有插入日志、增加計時邏輯來檢測性能、加入事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。
第二:從Python的語法層面上(其實第二種本質上也是第一種,只不過在語法上進行了規(guī)范化)
簡言之,python裝飾器就是用于拓展原來函數(shù)功能的一種函數(shù),這個函數(shù)的特殊之處在于它的返回值也是一個函數(shù),使用python裝飾器的好處就是在不用更改原函數(shù)的代碼前提下給函數(shù)增加新的功能。 如此一來,我們要想拓展原來函數(shù)功能,就不需要再在函數(shù)里面修改源代碼了。
02 什么是裝飾器decorator
2.2 裝飾器的作用——兩個層面
通過上面的表述,得出兩個最基本的結論,其實這也是裝飾器兩個最基本的作用:
(1)抽離雷同代碼,加以重用
(2)為函數(shù)添加額外的功能
02 什么是裝飾器decorator
2.3 裝飾器的使用場景
裝飾器可能在我們平時的編碼中比較少去自己定義,更多的是我們使用別人的已經(jīng)編寫好的裝飾器,比如我們我們經(jīng)常使用的@staticmethod,@classmethod,@property等等,都是別人寫好了,我們自己不需要自己再實現(xiàn)了,在編碼中,我們在下面的一些情況會經(jīng)常遇見裝飾器。
(1)緩存裝飾器
(2)權限驗證裝飾器
(3)計時裝飾器
(4)日志裝飾器
(5)路由裝飾器
(6)異常處理裝飾器
(7)錯誤重試裝飾器
后面我還會講到關于python的高級語法——python描述符(descriptor),其實也是跟python的裝飾器有著千絲萬縷的關系,詳細可以參見后面的文章哦!
03
裝飾器decorator的實現(xiàn)
03 裝飾器decorator的實現(xiàn)
3.1 裝飾器的逐步實現(xiàn)
針對上面改進版的代碼所存在的哪些問題,我們想出了解決辦法:
既然修改N處的調用代碼很麻煩,我們就來想想辦法不修改調用代碼;如果不修改調用代碼,也就意味著調用myfunc()需要產(chǎn)生調用timeit(myfunc)的效果。
因為python中一切皆對象,故而我們可以想到將timeit賦值給myfunc,
import time
def myfunc():
print("我是函數(shù)myfunc")
def timeit(function):
start = time.clock()
function()
end =time.clock()
print(f'函數(shù)執(zhí)行所花費的時間為:{end-start}')
myfunc=timeit #將timeit賦值給原來的myfunc
myfunc()
但是上面的調用并不會成功,會顯示出如下錯誤:
timeit() missing 1 required positional argument: 'function'
為什么呢?這是因為將timeit賦值給myfunc之后,此時myfunc和timeit表示的同一個東西,但是timeit似乎帶有一個參數(shù)function,而在調用myfunc()的時候并沒有傳入任何參數(shù),所以并不會成功。
但是上面的調用雖然沒有成功,卻給我們指出了一條重要的線索,因為上面的代碼已經(jīng)解決“修改調用代碼”的問題,只不過是參數(shù)沒有統(tǒng)一而已,那就想辦法把參數(shù)統(tǒng)一吧!那就再添加一個函數(shù)唄!什么意思?
因為參數(shù)不統(tǒng)一,如果timeit()不并不是直接添加額外的功能,而是返回一個與myfunc參數(shù)列表一致的函數(shù)。而原來timeit需要添加額外功能的代碼再在timeit里面定義一個函數(shù),由它去完成不就可以了嗎,將timeit(myfunc)的返回值賦值給myfunc,然后,調用myfunc()的代碼完全不用修改。——即我們依然是調用myfunc(調用代碼沒變),但是同樣卻達到了添加額外功能的效果!
代碼如下:
import time
#原來的函數(shù)myfunc
def myfunc():
print("我是函數(shù)myfunc")
#定義一個計時器
def timeit(function):
'''
timeit函數(shù)負責返回一個wrapper,wrapper的參數(shù)要與原來的myfunc保持相同
這樣一來,執(zhí)行 myfunc=timeit(myfunc) myfunc完全等價于wrapper
wrapper函數(shù)負責添加額外功能
'''
def wrapper():
start = time.clock()
function()
end =time.clock()
print(f'函數(shù)執(zhí)行所花費的時間為:{end-start}')
return wrapper
myfunc=timeit(myfunc) #注意,這里與前面的 “myfunc=timeit”是有所區(qū)別的哦
myfunc() #還和原來調用myfunc()一樣,但是達到了添加額外功能的效果
上面的運行結果就出來了,如下:
我是函數(shù)myfunc
函數(shù)執(zhí)行所花費的時間為:0.0005973331798019136
總結:在上面的函數(shù)定義和調用中,看起來我的調用myfunc()和原來并沒有任何不同,但是卻已經(jīng)添加了額外的效果。它解決前面存在的兩個問題:
(1)不用修改函數(shù)源代碼,也不用修改調用函數(shù)的代碼,完全跟調用最原始的myfunc()代碼一樣,但是卻添加了額外功能;
(2)解決了timeit和myfunc的參數(shù)不統(tǒng)一問題,那就是再添加一層wrapper;
——這就是裝飾器。
上面的裝飾器就是最原始的版本,但是python中引入了專門的“語法糖”來實現(xiàn)裝飾器,這樣看起來更加專業(yè),更加美觀。就是使用字符“@”去實現(xiàn)。代碼如下:
import time
#定義一個計時器
def timeit(function):
'''
timeit函數(shù)負責返回一個wrapper,wrapper的參數(shù)要與原來的myfunc保持相同
這樣一來,執(zhí)行 myfunc=timeit(myfunc) myfunc完全等價于wrapper
wrapper函數(shù)負責添加額外功能
'''
def wrapper():
start = time.clock()
function()
end =time.clock()
print(f'函數(shù)執(zhí)行所花費的時間為:{end-start}')
return wrapper
#myfunc=timeit(myfunc) #注意,這里與前面的 “myfunc=timeit”是有所區(qū)別的哦
#原來的函數(shù)myfunc
@timeit
def myfunc():
print("我是函數(shù)myfunc")
myfunc() #還和原來調用myfunc()一樣,但是達到了添加額外功能的效果
上面代碼的運行結果依然是:
我是函數(shù)myfunc
函數(shù)執(zhí)行所花費的時間為:0.0004893814003196401
在上面的例子中,在定義myfunc函數(shù)的上面加了一個@timeit,這與前面的寫法myfunc = timeit(myfunc)完全等價,
@有兩個重要的作用,
第一:較少了代碼書寫量;
第二:那就是讓我們的代碼看上去更有裝飾器的感覺,看起來更高端了。
總結
在這個例子中,函數(shù)進入和退出時需要計時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習慣的從上往下執(zhí)行方式相比較而言,像是在函數(shù)執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務領域里,能減少大量重復代碼。面向切面編程還有相當多的術語,這里就不多做介紹,感興趣的話可以去找找相關的資料(如果有需要,我后面也會抽時間專門寫一系列關于面向切面編程的文章,看我有沒有時間啦!)
03 裝飾器decorator的實現(xiàn)
3.2 裝飾器的一般“模板”
為了能夠明確裝飾器的實現(xiàn)原理,這里給出一個關于裝飾器的“一般模板”,方便大家理解!但是,裝飾器作為一種設計模式,本身是沒有固定的設計模板的,語法也是相對較為靈活,沒有說一定要怎么寫才正確。
模板如下:
def decorator(function):
'''
第一層函數(shù)為裝飾器名稱
function:參數(shù),即需要裝飾的函數(shù)
return:返回值wrapper,為了保持與原函數(shù)參數(shù)一致
'''
def wrapper(*arg,**args):
'''
內(nèi)層函數(shù),這個函數(shù)實現(xiàn)“添加額外功能”的任務
*arg,**args:參數(shù)保持與需要裝飾的函數(shù)參數(shù)一致,這里用*arg和**args代替
'''
#這里就是額外功能代碼
function() #執(zhí)行原函數(shù)
#這里就是額外功能代碼
return wrapper
一般就按照上面這個模板寫“裝飾器”函數(shù),一般就不會出錯了。
注意事項:
(1)裝飾器一般由兩層函數(shù)組成,外層的decorator,和內(nèi)層的wrapper;
(2)第一層函數(shù)的參數(shù)function,即需要裝飾的函數(shù),返回值wrapper,為了保持與原函數(shù)參數(shù)一致
(3)內(nèi)層函數(shù),這個函數(shù)實現(xiàn)“添加額外功能”的任務, *arg,**args:參數(shù)保持與需要裝飾的函數(shù)參數(shù)一致,這里用*arg和**args代替。
04
裝飾器的各種花式實現(xiàn)
04 裝飾器的各種花式實現(xiàn)
4.1 裝飾器的分類型實現(xiàn)
學過裝飾器的人都知道Python的閉包,關于“閉包”的詳細定義有各種版本,但我們經(jīng)常看見這樣一句話,“Python的裝飾器就是一種閉包或者是Python的閉包其實就是裝飾器”,這句話在一定程度上是不正確的,但是這么說也可以(心里要明白二者的本質)。
本質:python閉包是裝飾器的真子集,即裝飾器是更加寬泛的概念,至于為什么,它們二者的區(qū)別和聯(lián)系,我會在下一篇文章里面詳細說明。下一篇參考:
Python高級編程——裝飾器Decorator詳解(下篇)
不僅如此,上面所實現(xiàn)的裝飾器是針對函數(shù)的,實際上Python的裝飾器可以是“函數(shù)”或者是“類”,而被裝飾的對象也可以是“函數(shù)”或者是“類”,這樣一來,就有四種搭配情況,即:
函數(shù)裝飾函數(shù)
函數(shù)裝飾類
類裝飾函數(shù)
類裝飾類
具體每一種怎么實現(xiàn)呢?其實他們的設計思想都是大同小異,只是實現(xiàn)細節(jié)略有不同,欲知詳細情況,且聽下回分解!!!
下一篇預告:
裝飾器與閉包的聯(lián)系和區(qū)別
四大類裝飾器的搭配實現(xiàn)
····
本文后面分享三本python使用的電子書,分別是獲取方式:關注轉發(fā)文章私信小編(學習)就可以了
《python面試寶典》《python核心編程第三版》《利用python進行數(shù)據(jù)分析第二版》
獲取方式:關注轉發(fā)文章私信小編(學習)就可以了
總結
以上是生活随笔為你收集整理的python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust为什么显示不了国服_Rust编程
- 下一篇: 用python做自我介绍_python入