本文始發於我的公衆號:TechFlow,原創不易,求個關注程序員
今天是Python專題的第13篇文章,上一篇文章當中咱們介紹了Python裝飾器的定義和基本的用法,這篇文章咱們一塊兒來學習一下Python裝飾器的一些進階使用方法。對裝飾器不太熟悉,或者錯過了上篇內容的小夥伴能夠點擊下方傳送門。web
以前的文章當中咱們從前到後仔細推到了一下裝飾器的本質和用途,也學會了它的基本用法,已經足夠應付80%的場景了。可是總有20%的場景使用基本的方法解決不了,這個時候就須要咱們學習更多、更全的其餘用法。編程
好比我想要經過一個參數控制裝飾器的功能,這個問題其實很常見。就拿記錄時間來講,咱們都知道時間能夠記錄成不少種格式,好比能夠記成2020-05-04也能夠記錄成20200504,還能夠記錄成04/05/2020,若是是後端還會記錄時間的時間戳。好比說咱們如今實現了一個記錄日誌的裝飾器,用來給咱們的方法打上日誌,如今咱們想要控制記錄日誌的時候打印出來的時間格式,這個需求使用最簡單的裝飾器就沒有辦法解決了。後端
這個時候,若是想要解決問題,就必須引入參數,也就是說咱們必需要在裝飾器當中加入參數才行。但問題來了,這個參數怎麼加,加在哪裏呢?閉包
在咱們介紹具體的用法以前,咱們先來回顧一下裝飾器的代碼:編輯器
函數式編程
def mydec(func):
@wraps(func)
def mywrap(*args, **kw):
print('hello this is decorator1')
func(*args, **kw)
return mywrap
複製代碼@mydec def helloWorld(): print('hello, world') 複製代碼
這個就是咱們上次講的最簡單的那種裝飾器,假如說咱們這個時候但願傳入一個參數type,能夠控制裝飾器的輸出結果。就像這樣:函數
@mydec(type_='test')
def helloWorld():
print('hello, world')
複製代碼
咱們可能會想是否是應該在mydec這個方法的參數裏面加上一個type_,可是若是你試一下就好發現這樣是不行的,會獲得一個error:工具
Error錯誤的字面意思很好理解,可是緣由卻使人費解。這個Error是說函數mydec少了一個必選參數func,這個func就是咱們要包裝的函數,可是這個不是自動傳入的嗎,怎麼會提示咱們少了這個參數呢?
若是這個問題的本質不能理解的話,那麼裝飾器就很難大成了,由於只有理解清楚了這一點,才能理解後面裝飾器各類稀奇古怪的進階用法。可是很坑爹的是,不少資料當中都只是簡單地介紹了怎麼用,不多會探究其中背後的緣由,這會讓初學者在學習的時候陷入費解。我在學習的時候也花了不少心思,才終於搞明白,說穿了很簡單,可是想通不容易。
其實這樣會報錯的主要緣由是註解當中有參數和沒有參數的裝飾器是徹底不一樣的。
咱們來回顧一下不加參數的裝飾器的用法,好比:
@mydec
def hello_world():
pass
複製代碼
咱們執行hello_world()的時候,等價於執行mydec(hello_world)()。看明白了嗎,咱們把這行代碼展開,它實際上是下面這兩行代碼共同執行的結果:
cur = mydec(hello_world)
cur()
複製代碼
若是hello_world這個函數帶上參數呢?
@mydec
def hello_world(*args, **kw):
pass
複製代碼
那麼執行的時候它實際上是這樣的:
cur = mydec(hello_world)
cur(*args, **kw)
複製代碼
這個理解了以後,咱們繼續往下,如今咱們想要將一個參數傳給裝飾器,按照咱們的想法下面這兩段代碼應該是同樣的。
@mydec(type_='test')
def helloWorld():
print('hello, world')
cur = mydec(hello_world, type_) cur() 複製代碼
可是很遺憾的是,Python解釋器當中並非這麼設計的。它對加上了參數的裝飾器多作了一層封裝,也就是說上面傳入參數的hello_world函數執行的時候等價於下面這段代碼:
cur1 = mydec(type_)
cur2 = cur1(hello_world)
cur2()
複製代碼
正是由於額外多封裝了一層,因此函數和裝飾器的參數傳入裝飾器的順序是不一樣的,順序也是不同的。明白了這點以後就簡單不少了,既然Python解釋器在解釋裝飾器參數的時候多增長了一層,那麼若是咱們想要實現帶參數的裝飾器,只須要也在裝飾器當中多封裝一層就能夠了。好比能夠寫成這樣:
def mydec(type_=None):
def decorate(func):
@wraps(func)
def mywrap():
if type_ is not None:
print(type_)
func()
return mywrap
return decorate
複製代碼
這樣咱們再執行就能夠了:
到這裏看似一切都很完美,但其實有一個很大的問題被咱們忽略了。
這個問題就是默認參數問題,在前面咱們定義裝飾器的時候,將type_這個參數設置成了可選的。這也很符合咱們實際狀況,如非必要,參數能省略就省略。可是這就致使了一個問題,對於不用加上參數的裝飾器,有些人習慣寫成mydec(),有些人習慣寫成mydec。若是咱們試一下mydec,就會發現這樣寫會報錯:
這個報錯和上面的報錯如出一轍,出現的緣由也是同樣的,都是少了func參數。可是很奇怪啊,爲何會少了func呢?
緣由很簡單,由於咱們把括號去掉,裝飾器又回到了以前的兩層結構!
cur = mydec(hello_world)
cur(*args, **kw)
複製代碼
這就很坑爹了,咱們裝飾器的結構確定是不能改變的,若是使用兩層結構就沒辦法傳入參數了,可是若是不傳參的時候怎麼辦,難道就只能強制程序員統一風格所有加上括號嗎?這固然也是一個辦法,那還有沒有更好的辦法呢?有沒有辦法統一這兩種邏輯呢?
固然是有的,爲了解決這個問題,咱們須要用到一個新的工具,叫作偏函數。
偏函數很好理解,它本意也是一個高階函數,其實就是閉包。偏函數的使用場景針對多參數的函數,經過使用偏函數,能夠固定若干個參數的傳值,從而起到簡化函數傳參的做用。咱們來看一個例子,咱們建立一個pow函數,用來計算x的n次方:
import math
def pow(x, n):
return math.pow(x, n)
複製代碼
這個函數須要傳入x和n兩個參數,若是咱們當前只須要計算平方,咱們可使用閉包,固定其中的參數n,生成一個新的函數來作到這點。好比:
def mypow(n):
def func(x):
return pow(x, n)
return func
複製代碼
偏函數的本質就是這樣一個閉包,只不過它簡化了咱們的代碼而已:
from functools import partial
pow2 = partial(pow, n=2) pow2(6) 複製代碼
使用偏函數咱們只須要傳入待加工的原函數,以及固定的參數值便可。咱們把偏函數用在裝飾器當中,就能夠解決剛纔的問題。回憶一下,不帶參數的裝飾器是兩層函數嵌套,而帶上參數的是三層嵌套。那麼咱們使用partial,專門爲帶上參數的狀況額外增長一層嵌套便可:
def mydec(func=None, type_=None):
# 不帶參數的話,func會是None,這時候咱們固定參數便可
if func is None:
return partial(mydec, type_=type_)
複製代碼 @wraps(func) def mywrap(): if type_ is not None: print(type_) func() return mywrap 複製代碼
咱們來看下這其中的細節,當咱們不傳入參數的時候,咱們其實執行的是cur = mydec(func),這個時候func不爲空,那麼不會觸發if中的語句,因此會直接返回mywrap。若是傳入參數,這時候func是None,會觸發if中的partial。注意這裏咱們在partial當中傳入的函數依然是mydec,也就是說咱們固定了type_這個參數,調用的話依然返回的是mywrap,至關於咱們經過partial額外在兩層結構當中專門爲帶參數的狀況增長了一層,統一了邏輯。
今天的概念比以前的裝飾器要複雜不少,一時可能並很差理解,其實這是很是正常的。這不只僅是裝飾器的問題,也不只是Python的問題,歸根結底這是函數式編程的特性致使的。函數式編程的優勢就是高度靈活,使用很是方便,但缺點也很明顯,代碼難以維護,閱讀難度高,理解起來也不簡單。典型的初學簡單,精深很是難的典型。因此若是你們以爲一時理解不了,這並非大家的問題,一方面咱們須要培養本身函數傳編程的思惟,另外一方面咱們也須要熟悉Python中裝飾器的使用方法。
最後說點題外話,因爲只狼和仁王,最近有點迷上了硬核遊戲。剛開始玩的時候,以爲很是困難,常常卡關,一個boss死個幾十次是屢見不鮮。等到了後來,慢慢找到了訣竅,瞬間發現這類遊戲甚至全部遊戲都變得簡單了。
這不只僅是我熟悉了,更多的是由於玩遊戲的時候也開始思考了,開始思考這些boss設計了哪些招數?設計者給咱們留下了哪些操做的空間對付它?有哪些規律可循?思考的多了,訣竅也就有了。打多了以後,不少boss就只剩下了初見難,只要打個兩三次熟悉了套路,就能夠過關了。慢慢地我發現生活當中的不少事情其實和遊戲中的boss同樣,只是初見難,第一次見到的時候以爲無從下手,以爲難以理解,以爲龐然大物,因此很難。但只要有一顆堅毅、勇敢的心,學會冷靜理智去分析,其實不過只是紙老虎而已。
但願能給你們一點小小的啓發,但願你們面前的困難都只是紙老虎,但願你們都能找到本身的勇氣。
今天的文章就到這裏,原創不易,須要你的一個關注,你的舉手之勞對我來講很重要。