首先上原文, 現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。本質上,decorator就是一個返回函數的高階函數。 Decorator本質是高階函數? 不信 ...
首先上原文,
現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。
Decorator本質是高階函數?
不信邪的我試了下..
1 def g(): 2 print("這裡是G") 3 return "G" 4 5 @g 6 def f(): 7 print("這裡是F") 8 return 1 9 ''' 10 -------------------------------------------- 11 line 5, in <module> 12 @g 13 TypeError: g() takes 0 positional arguments but 1 was given 14 >>> 15 '''
運行結果在註釋里
尷尬了...g被強制塞了個參數,那個參數應該是g"修飾的對象"
修改下繼續.....
def g(f): print("這裡是G") return "G" @g def f(): print("這裡是F") return 1 ''' -------------------------------------------- 這裡是G >>> f() Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> f() TypeError: 'str' object is not callable '''
str對象不能被調用,,,這裡的str只有一個,就是g的返回值
為了驗證下,我把"G"改成了2
結果是
TypeError: 'int' object is not callable
OK了,大概意思就是,裝飾器首先被"塞"一個參數,然後,返回值還要被調用一次,然而好像只有函數才能被調用,,所以,為了不報錯,裝飾器必須返回一個函數,裝飾器必須是高階函數......
我表示不服( ̄へ ̄),不就是函數嗎,g的參數就是函數,,,騷操作上腦ing
def g(f): print("這裡是G") return f @g def f(): print("這裡是F") return 1 ''' -------------------------------------------- 這裡是G >>> f() 這裡是F '1' '''
運行成功.但是....說好的"列印日誌功能"呢,#沒錯"這裡是G"就是我想要的日誌
疑點: 列印的"這裡是G"是第一行,是在輸入"f()"之前發生的....
#單看g函數,它不算是"高階函數"
作為成功的例子,它太失敗了┐( ̄ヘ ̄)┌
##好吧,我投降了,負隅頑抗也不怎麼有意思.....
閉包告訴我們一個道理,,,為了保證返回值一定是函數,最好的措施就是"在函數內部,現場造一個函數然後扔出去"
def g(f): print("這裡是G") def h(): print('這裡是H') return "H" return h @g def f(): print("這裡是F") return 1 ''' -------------------------------------------- 這裡是G >>> f() 這裡是H 'H' >>> f() 這裡是H 'H' >>> '''
f函數不執行了,,是的沒錯,我還多試了一次的
另外,兩條日誌只有裡面的能用......(下文會解釋的)
看了一下書,h函數返回f()的話,f函數就能被執行了,,,,,個人感覺裝飾器應該叫"劫持器"
def g(f): print("這裡是G") def h(): print('這裡是H') return f() return h @g def f(): print("這裡是F") return 1 ''' -------------------------------------------- 這裡是G >>> f() 這裡是H 這裡是F '1' >>> f <function g.<locals>.h at 0x0000020CBDBB6C80> '''
按書上的思路解釋下
''' @g def f(): pass >>>f() 等價於 >>>g(f) () g函數執行,返回 >>>h () h函數執行(列印日誌) >>>f() f執行,返回1 >>>1 '''
加上參數,
def g(f): print("這裡是G") def h(*args,**kw): print('這裡是H') return f(*args,**kw) return h @g def f(*args,**kw): print("這裡是F") return 1 ''' >>>f(*args,**kw) 等價於 >>>g(f) (*args,**kw) g函數執行,返回 >>>h (*args,**kw) h函數執行(列印日誌) >>>f(*args,**kw) f執行,返回1 >>>1 '''
可以看出,(*args,**kw)本改被h函數拿走,所以,觀察h函數,h把它的參數原封不動的交給了f
機智的我動了歪主意
def g(f): print("這裡是G") def h():#h沒要求參數 print('這裡是H') return f return h @g def f(*args,**kw): print("這裡是F") return 1 ''' >>>f()(*args,**kw) 等價於 >>>g(f) ()(*args,**kw) g函數執行,返回 >>>h ()(*args,**kw) h函數執行,h拿的空參數 (列印日誌) >>>f(*args,**kw) f執行,返回1 >>>1 '''
可是新的問題來了,後面f調用的時候得多加個空括弧,否則
''' >>>f(*args,**kw) 等價於 >>>g(f) (*args,**kw) g函數執行,返回 >>>h (*args,**kw) h函數執行(列印日誌)返回f >>>f 這是一個函數對象 '''
以上告訴我們一個道理"函數執行不執行取決於後面有沒有括弧"
舉個例子
def m(a): print(a) return m print(m(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)) ''' ----------------------------- 1 2 3 4 5 6 7 8 9 10 <function m at 0x000002832BDB10D0>
燒腦時刻
f = a.b.c()()[0]()[d()()[e]]
a模塊的b類的c方法是高階函數,最終返回一個列表,列表裡有個函數
函數又返回一個字典............
下麵開始,,裝飾器的三層嵌套
它要解決的問題是裝飾器需要額外的參數怎麼辦
就是g(m)變成了g(canshu)(m)多了一個括弧,那麼就得多加一層函數,,否則就會出錯
>>> 1() 1() TypeError: 'int' object is not callable
.修改後的
def i(canshu): def g(f): print("這裡是G") def h(*args,**kw): print('這裡是H') return f(*args,**kw) return h return g @i('canshu') def f(*args,**kw): print("這裡是F") return 1 ''' -------------------------------------------- 這裡是G >>> f() 這裡是H 這裡是F '1' >>> f <function i.<locals>.g.<locals>.h at 0x00000283A6886D08> '''
解釋一下
''' >>>f(*args,**kw) 等價於 >>>i('canshu')(f)(*args,**kw) i函數返回g >>>g(f)(*args,**kw) g函數執行,返回h >>>h(*args,**kw) h函數執行(列印日誌)返回f(*args,**kw) >>>f(*args,**kw) f執行返回1 >>>f 等價於 >>>i('canshu')(f) i函數返回g >>>g(f) g函數執行,返回h >>>h 這是個函數對象 '''
所以
>>> f.__name__
'h'
真是劫持器呀,__name__都變了...
至於為什麼每次最開始都有一個
應該可以這樣理解
執行@i('canshu')語句時候裝飾器被執行了一次
''' >>>i('canshu')(f) i函數返回g >>>g(f) g函數執行,列印了一句,返回h >>>h 函數對象 '''
驗證
def i(canshu): def g(f): print(f.__name__) print('這裡是G') def h(*args,**kw): print('這裡是H') return f(*args,**kw) return h return g @i('canshu') def f(*args,**kw): print("這裡是F") return 1 ''' -------------------------------------------- f 這裡是G >>> f() 這裡是H 這裡是F '1' '''
綜上,新啟示
- 裝飾器只有最內層的內容能起"日誌"的作用,其它層的內容會在定義時執行
- 只要設計得當,一個函數後面可以跟好多括弧,函數層數和括弧數得儘量一致
另外,"劫持器"還可以用於調試,當兩個函數不知道哪個更好時
def i(f_1): def g(f): def h(*args,**kw): print('%s把%s替換了'%(f_1.__name__,f.__name__)) return f_1(*args,**kw) return h return g def f_1(*args,**kw): print("這裡是F_1") return 2 @i(f_1) def f(*args,**kw): print("這裡是F") return 1 ''' -------------------------------------------- >>> f() f_1把f替換了 這裡是F_1 2 '''
f=f_1語句的加日誌版φ(>ω<*)
應該完了,,,
有什麼遺漏以後再補
突然覺得這篇隨筆處於"小白看不懂,大神不屑一顧"的尷尬境地.....
.