您好,登錄后才能下訂單哦!
是不是很多伙伴都認為Python的語法簡單,作為入門語言學起來非常簡單?
很多伙伴說Python寫出來的代碼只要符合邏輯,不需要太多的學習即可,即可從一門其他語言跳來用Python寫(當然這樣是好事,誰都希望入門簡單)。
于是我便記錄一下,如果要學Python的話,到底有什么好學的。記錄一下Python有什么值得學的,對比其他語言有什么特別的地方,有什么樣的代碼寫出來更Pythonic。一路回味,一路學習。
什么是修飾器,為什么叫修飾器
修飾器英文是Decorator,
我們假設這樣一種場景:古老的代碼中有幾個很是復雜的函數F1、F2、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的效果。
這段代碼目前沒有什么問題,接下來煎魚加需求:需求為幾十個函數都加上這樣的前后的功能,而所有調用這些函數地方也要相應地升級。
看起來這個需求比較扯,偏偏這個需求卻是較為廣泛:在調用函數的前后加上log輸出、在調用函數的前后計算調用時間、在調用函數的前后占用和釋放資源等等。
一種比較笨的方法就是,為這幾十個函數逐一添加一個入口函數,針對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()
大家可以大膽猜測一下結果會怎樣。。。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。