您好,登錄后才能下訂單哦!
是不是很多伙伴都認(rèn)為Python的語法簡單,作為入門語言學(xué)起來非常簡單?
很多伙伴說Python寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語言跳來用Python寫(當(dāng)然這樣是好事,誰都希望入門簡單)。
于是我便記錄一下,如果要學(xué)Python的話,到底有什么好學(xué)的。記錄一下Python有什么值得學(xué)的,對(duì)比其他語言有什么特別的地方,有什么樣的代碼寫出來更Pythonic。一路回味,一路學(xué)習(xí)。
什么是修飾器,為什么叫修飾器
修飾器英文是Decorator,
我們假設(shè)這樣一種場景:古老的代碼中有幾個(gè)很是復(fù)雜的函數(shù)F1、F2、F3…,復(fù)雜到看都不想看,反正我們就是不想改這些函數(shù),但是我們需要改造加功能,在這個(gè)函數(shù)的前后加功能,這個(gè)時(shí)候我們很容易就實(shí)現(xiàn)這個(gè)需求:
def hi(): """hi func,假裝是很復(fù)雜的函數(shù)""" return 'hi' def aop(func): """aop func""" print('before func') print(func()) print('after func') if __name__ == '__main__': aop(hi)
以上是很是簡單的實(shí)現(xiàn),利用Python參數(shù)可以傳函數(shù)引用的特性,就可以實(shí)現(xiàn)了這種類似AOP的效果。
這段代碼目前沒有什么問題,接下來煎魚加需求:需求為幾十個(gè)函數(shù)都加上這樣的前后的功能,而所有調(diào)用這些函數(shù)地方也要相應(yīng)地升級(jí)。
看起來這個(gè)需求比較扯,偏偏這個(gè)需求卻是較為廣泛:在調(diào)用函數(shù)的前后加上log輸出、在調(diào)用函數(shù)的前后計(jì)算調(diào)用時(shí)間、在調(diào)用函數(shù)的前后占用和釋放資源等等。
一種比較笨的方法就是,為這幾十個(gè)函數(shù)逐一添加一個(gè)入口函數(shù),針對(duì)a函數(shù)添加一個(gè)a_aop函數(shù),針對(duì)b函數(shù)添加一個(gè)b_aop函數(shù)…如此這樣。 問題也很明顯:
于是接下來有請(qǐng)修飾器出場,修飾器可以統(tǒng)一地給這些函數(shù)加這樣的功能:
def aop(func): """aop func""" def wrapper(): """wrapper func""" print('before func') func() print('after func') return wrapper @aop def hi(): """hi func""" print('hi') @aop def hello(): """hello func""" print('hello') if __name__ == '__main__': hi() hello()
以上aop函數(shù)就是修飾器的函數(shù),使用該修飾器時(shí)只要在待加函數(shù)上一行加@修飾器函數(shù)名即可,如實(shí)例代碼中就是@aop。
加上了@aop后,調(diào)用新功能的hi函數(shù)就喝原來的調(diào)用一樣:就是hi()而不是aop(hi),也意味著所有調(diào)用這些函數(shù)的地方不需要修改就可以升級(jí)。
簡單地來說,大概修飾器就是以上的這樣子。
@是個(gè)什么
對(duì)于新手來說,上面例子中, @就是一樣奇怪的東西:為什么這樣子用就可以實(shí)現(xiàn)需求的功能了。
其實(shí)我們還可以不用@,這里換一種寫法:
def hi(): """hi func""" print('hi') def aop(func): """aop func""" def wrapper(): """wrapper func""" print('before func') func() print('after func') return wrapper if __name__ == '__main__': hi() print('') hi = aop(hi) hi()
上面的例子中的aop函數(shù)就是之前說過的修飾器函數(shù)。
如例子main函數(shù)中第一次調(diào)用hi函數(shù)時(shí),由于hi函數(shù)沒叫修飾器,因此我們可以從輸出結(jié)果中看到程序只輸出了一個(gè)hi而沒有前后功能。
然后加了一個(gè)hi = aop(hi)后再調(diào)用hi函數(shù),得到的輸出結(jié)果和加修飾器的一樣,換言之:
@aop 等效于hi = aop(hi)
因此,我們對(duì)于@,可以理解是,它通過閉包的方式把新函數(shù)的引用賦值給了原來函數(shù)的引用。
有點(diǎn)拗口。aop(hi)是新函數(shù)的引用,至于返回了引用的原因是aop函數(shù)中運(yùn)用閉包返回了函數(shù)引用。而hi這個(gè)函數(shù)的引用,本來是指向舊函數(shù)的,通過hi = aop(hi)賦值后,就指向新函數(shù)了。
被調(diào)函數(shù)加參數(shù)
以上的例子中,我們都假設(shè)被調(diào)函數(shù)是無參的,如hi、hello函數(shù)都是無參的,我們?cè)倏匆谎奂弭~剛才的寫的修飾器函數(shù):
def aop(func): """aop func""" def wrapper(): """wrapper func""" print('before func') func() print('after func') return wrapper
很明顯,閉包函數(shù)wrapper中,調(diào)用被調(diào)函數(shù)用的是func(),是無參的。同時(shí)就意味著,如果func是一個(gè)帶參數(shù)的函數(shù),再用這個(gè)修飾器就會(huì)報(bào)錯(cuò)。
@aop def hi_with_deco(a): """hi func""" print('hi' + str(a)) if __name__ == '__main__': # hi() hi_with_deco(1)
就是參數(shù)的問題。這個(gè)時(shí)候,我們把修飾器函數(shù)改得通用一點(diǎn)即可,其中import了一個(gè)函數(shù)(也是修飾器函數(shù)):
from functools import wraps def aop(func): """aop func""" @wraps(func) def wrap(*args, **kwargs): print('before') func(*args, **kwargs) print('after') return wrap @aop def hi(a, b, c): """hi func""" print('test hi: %s, %s, %s' % (a, b, c)) @aop def hello(a, b): """hello func""" print('test hello: %s, %s' % (a, b)) if __name__ == '__main__': hi(1, 2, 3) hello('a', 'b')
這是一種很奇妙的東西,就是在寫修飾器函數(shù)的時(shí)候,還用了別的修飾器函數(shù)。那也沒什么,畢竟修飾器函數(shù)也是函數(shù)啊,有什么所謂。
帶參數(shù)的修飾器
思路到了這里,煎魚不禁思考一個(gè)問題:修飾器函數(shù)也是函數(shù),那函數(shù)也是應(yīng)該能傳參的。函數(shù)傳參的話,不同的參數(shù)可以輸出不同的結(jié)果,那么,修飾器函數(shù)傳參的話,不同的參數(shù)會(huì)怎么樣呢?
其實(shí)很簡單,修飾器函數(shù)不同的參數(shù),能生成不同的修飾器啊。
如,我這次用這個(gè)修飾器是把時(shí)間日志打到test.log,而下次用修飾器的時(shí)候煎魚希望是能打到test2.log。這樣的需求,除了寫兩個(gè)修飾器函數(shù)外,還可以給修飾器加參數(shù)選項(xiàng):
from functools import wraps def aop_with_param(aop_test_str): def aop(func): """aop func""" @wraps(func) def wrap(*args, **kwargs): print('before ' + str(aop_test_str)) func(*args, **kwargs) print('after ' + str(aop_test_str)) return wrap return aop @aop_with_param('abc') def hi(a, b, c): """hi func""" print('test hi: %s, %s, %s' % (a, b, c)) @aop_with_param('pppppp') def hi2(a, b, c): """hi func""" print('test hi: %s, %s, %s' % (a, b, c)) if __name__ == '__main__': hi(1, 2, 3) print('') hi2(2, 3, 4)
同樣的,可以加一個(gè)參數(shù),也可以加多個(gè)參數(shù),這里就不說了。
修飾器類
大道同歸,邏輯復(fù)雜了之后,人們都喜歡將函數(shù)的思維層面抽象上升到對(duì)象的層面。原因往往是對(duì)象能擁有多個(gè)函數(shù),對(duì)象往往能管理更復(fù)雜的業(yè)務(wù)邏輯。
顯然,修飾器函數(shù)也有對(duì)應(yīng)的修飾器類。寫起來也沒什么難度,和之前的生成器一樣簡單:
from functools import wraps class aop(object): def __init__(self, aop_test_str): self.aop_test_str = aop_test_str def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('before ' + self.aop_test_str) func() print('after ' + self.aop_test_str) return wrapper @aop('pppppp') def hi(): print('hi')
看得出來,這個(gè)修飾器類也不過是多了個(gè)__call__函數(shù),而這個(gè)__call__函數(shù)的內(nèi)容和之前寫的修飾器函數(shù)一個(gè)樣!而使用這個(gè)修飾器的方法,和之前也一樣,一樣的如例子中的@aop('pppppp')。
過于無聊,還試了一下繼承的修飾器類:
class sub_aop(aop): def __init__(self, sub_aop_str, *args, **kwargs): self.sub_aop_str = sub_aop_str super(sub_aop, self).__init__(*args, **kwargs) def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('before ' + self.sub_aop_str) super(sub_aop, self).__call__(func)() print('after ' + self.sub_aop_str) return wrapper @sub_aop('ssssss', 'pppppp') def hello(): print('hello') if __name__ == '__main__': hello()
大家可以大膽猜測一下結(jié)果會(huì)怎樣。。。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。