「Python有什麼好學的」這句話可不是反問句,而是問句哦。shell
主要是煎魚以爲太多的人以爲Python的語法較爲簡單,寫出來的代碼只要符合邏輯,不須要太多的學習便可,便可從一門其餘語言跳來用Python寫(固然這樣是好事,誰都但願入門簡單)。閉包
因而我便記錄一下,若是要學Python的話,到底有什麼好學的。記錄一下Python有什麼值得學的,對比其餘語言有什麼特別的地方,有什麼樣的代碼寫出來更Pythonic。一路回味,一路學習。app
修飾器英文是Decorator,函數
咱們假設這樣一種場景:古老的代碼中有幾個非常複雜的函數F一、F二、F3...,複雜到看都不想看,反正咱們就是不想改這些函數,可是咱們須要改造加功能,在這個函數的先後加功能,這個時候咱們很容易就實現這個需求:學習
def hi(): """hi func,僞裝是很複雜的函數""" return 'hi' def aop(func): """aop func""" print('before func') print(func()) print('after func') if __name__ == '__main__': aop(hi)
以上是非常簡單的實現,利用Python參數能夠傳函數引用的特性,就能夠實現了這種相似AOP的效果。spa
這段代碼目前沒有什麼問題,接下來煎魚加需求:需求爲幾十個函數都加上這樣的先後的功能,而全部調用這些函數地方也要相應地升級。日誌
看起來這個需求比較扯,恰恰這個需求倒是較爲普遍:在調用函數的先後加上log輸出、在調用函數的先後計算調用時間、在調用函數的先後佔用和釋放資源等等。code
一種比較笨的方法就是,爲這幾十個函數逐一添加一個入口函數,針對a函數添加一個a_aop函數,針對b函數添加一個b_aop函數...如此這樣。問題也很明顯:對象
因而接下來有請修飾器出場,修飾器能夠統一地給這些函數加這樣的功能:繼承
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函數就是修飾器的函數,使用該修飾器時只要在待加函數上一行加@修飾器函數名
便可,如實例代碼中就是@aop
。
加上了@aop
後,調用新功能的hi函數就喝原來的調用同樣:就是hi()
而不是aop(hi)
,也意味着全部調用這些函數的地方不須要修改就能夠升級。
簡單地來講,大概修飾器就是以上的這樣子。
對於新手來講,上面例子中,@
就是同樣奇怪的東西:爲何這樣子用就能夠實現煎魚需求的功能了。
其實咱們還能夠不用@
,煎魚換一種寫法:
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函數就是以前說過的修飾器函數。
如例子main函數中第一次調用hi函數時,因爲hi函數沒叫修飾器,所以咱們能夠從輸出結果中看到程序只輸出了一個hi而沒有先後功能。
而後煎魚加了一個hi = aop(hi)
後再調用hi函數,獲得的輸出結果和加修飾器的同樣,換言之:
@aop
等效於hi = aop(hi)
所以,咱們對於@
,能夠理解是,它經過閉包的方式把新函數的引用賦值給了原來函數的引用。
有點拗口。aop(hi)
是新函數的引用,至於返回了引用的緣由是aop函數中運用閉包返回了函數引用。而hi
這個函數的引用,原本是指向舊函數的,經過hi = aop(hi)
賦值後,就指向新函數了。
以上的例子中,咱們都假設被調函數是無參的,如hi、hello函數都是無參的,咱們再看一眼煎魚剛纔的寫的修飾器函數:
def aop(func): """aop func""" def wrapper(): """wrapper func""" print('before func') func() print('after func') return wrapper
很明顯,閉包函數wrapper中,調用被調函數用的是func()
,是無參的。同時就意味着,若是func是一個帶參數的函數,再用這個修飾器就會報錯。
@aop def hi_with_deco(a): """hi func""" print('hi' + str(a)) if __name__ == '__main__': # hi() hi_with_deco(1)
就是參數的問題。這個時候,咱們把修飾器函數改得通用一點便可,其中import了一個函數(也是修飾器函數):
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')
這是一種很奇妙的東西,就是在寫修飾器函數的時候,還用了別的修飾器函數。那也沒什麼,畢竟修飾器函數也是函數啊,有什麼所謂。
思路到了這裏,煎魚不由思考一個問題:修飾器函數也是函數,那函數也是應該能傳參的。函數傳參的話,不一樣的參數能夠輸出不一樣的結果,那麼,修飾器函數傳參的話,不一樣的參數會怎麼樣呢?
其實很簡單,修飾器函數不一樣的參數,能生成不一樣的修飾器啊。
如,我此次用這個修飾器是把時間日誌打到test.log
,而下次用修飾器的時候煎魚但願是能打到test2.log
。這樣的需求,除了寫兩個修飾器函數外,還能夠給修飾器加參數選項:
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)
一樣的,能夠加一個參數,也能夠加多個參數,這裏就不說了。
大道同歸,邏輯複雜了以後,人們都喜歡將函數的思惟層面抽象上升到對象的層面。緣由每每是對象能擁有多個函數,對象每每能管理更復雜的業務邏輯。
顯然,修飾器函數也有對應的修飾器類。寫起來也沒什麼難度,和以前的生成器同樣簡單:
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')
看得出來,這個修飾器類也不過是多了個__call__
函數,而這個__call__
函數的內容和以前寫的修飾器函數一個樣!而使用這個修飾器的方法,和以前也同樣,同樣的如例子中的@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()
大家猜猜結果怎麼樣?
先這樣吧
如有錯誤之處請指出,更多地請關注造殼。