1. 程式人生 > >python學習——裝飾器函數

python學習——裝飾器函數

復雜 完美 inner python python開發 之前 六大原則 修改 參數

一、裝飾器函數的作用是什麽

  答:裝飾器函數是在不修改原函數及其調用方式的情況下對原函數功能進行擴展

  對於搞python開發的人來說,函數占據了至關重要的地位。都說學好函數你就可以去找工作了,好了,假如你已經在某個公司上班了,想到馬上就要過年了,那過年就意味著你可以向老板要年終獎金了,那憑什麽老板要給你年終獎呢,你又能得到多少年終獎金呢。老板是這樣說的,你給我至少寫1000行代碼,我來看一下你代碼的執行時間,根據你的執行時間來評你的年終獎。好了,你知道既然要計算時間,學過函數的你就來實現一個計算函數執行時間的函數唄。聰明的你是這樣的。

技術分享圖片
 1 import time
 2 
 3
def qqxing(): 4 start = time.time() 5 print(老板好,同事好,大家新年好) 6 end = time.time() 7 elapsed_time = (end - start) 8 print(elapsed_time) 9 10 qqxing() 11 12 #運行結果:老板好,同事好,大家新年好 13 # 0.0
計算函數的執行時間

  實現了計算時間的函數,可是你發現了執行時間為0,因為你寫得函數太厲害了,但如果是0的話怕嚇到老板,所以你這樣搞。

技術分享圖片
 1 import time
 2 
 3 def qqxing():
 4     start = time.time()
 5     time.sleep(0.1)
 6     print(老板好,同事好,大家新年好)
 7     end = time.time()
 8     elapsed_time = (end - start)
 9     print(elapsed_time)
10 
11 qqxing()
12 
13 #運行結果:老板好,同事好,大家新年好
14 #          0.10044193267822266
計算函數的執行時間

  ok,雖然實現了,但是你寫了500個函數,你難道要每個函數都加上計算時間的函數嗎?能不能這樣搞?

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     start = time.time()
 5     func()
 6     end = time.time()
 7     print(end - start)
 8 
 9 def func1():
10     print(func1 over)
11 
12 def func2():
13     print(func2 over)
14 
15 timmer(func1)
16 timmer(func2)
計算多個函數的執行時間

  這樣是不是好多了,基本可以實現,但你會發現實際上當我調用func()函數時,調用方式改變了,由原來的func()變成timmer(func)了,那能不能不改變原函數的調用方式呢?可不可以這樣。

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     start = time.time()
 5     func()
 6     end = time.time()
 7     print(end - start)
 8 
 9 def func():
10     print(func over)
11 func = timmer
12 timmer()
不改變函數調用方式

  這樣就完美了,可惜我太天真,當執行函數時會報錯,因為timmer(func)要傳一個值,而你明目張膽的就給python解釋器來一個func = timmer,這哪行!而我們在用函數作為變量賦值的時候是沒法傳參數的。所以你沒法了。至此你得出了一個結論:年終獎與我無緣!?????? 但是你是一個不服輸的人,冥思苦想了1個小時。得到了如下一個強大的功能。

二、裝飾器的形成過程

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     def inner():
 5         start = time.time()
 6         func()
 7         end = time.time()
 8         print(end - start)
 9     return inner
10 def func():
11     time.sleep(0.1)
12     print(func over)
13 func = timmer(func)
14 func()
15 
16 #運行結果: func over
17 #           0.10010719299316406
一個強大的功能

  確實沒有改變func()的調用方式。這兒其實你應該知道你用了一個閉包函數,在這兒介紹一下閉包函數。

  什麽是閉包函數:即在內部函數中調用外部函數的變量,********註意:外部變量不包含全局變量**********

  但是有個地方很礙眼,就是這個func = timmer(func),你怕老板少給你年終獎,於是你到萬能的朋友圈,博客,百度一頓搜索,得到了一顆糖,叫“語法糖” ??

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     def inner():
 5         start = time.time()
 6         func()
 7         end = time.time()
 8         print(end - start)
 9     return inner
10 @timmer
11 def func():
12     time.sleep(0.1)
13     print(func over)
14 #func = timmer(func)
15 func()
語法糖

  這是一顆很甜的糖,你可開興了。於是你把語法糖的用法寫到了下面??

    語法糖的用法就是,@函數名 其中函數名是裝飾器函數的函數名,而且@函數名必須在被裝飾的函數上面,雖然他們沒有在一起,但他們就像親人一樣。??????

  然後你又小結了一下裝飾器??

    裝飾器的本質:一個閉包函數

    裝飾器的功能:在不修改原函數及其調用方式的情況下對原函數功能進行擴展。

  簡單的裝飾器如下:

 1 import time
 2 
 3 def timmer(func)
 4     def inner()
 5         start = time.time()
 6         func()
 7         print(time.time() - start)
 8     retuan inner
 9 
10 @timmer
11 def func()
12     print(hello wrapper)
13 
14 func()    

  其中,定義的timmer(func)是裝飾器函數,func()是被裝飾的函數,它必須緊緊地挨著語法糖,語法糖就相當於“@timmer ——> func = timmer(func) ”在timmer(func)裏面有一個inner(),在inner()裏面的func()前面和後面我們可以加一些必要的功能。這才是裝飾器真正的厲害之處。

三、裝飾器的進階

  上面我們得到了一個很low的裝飾器,但還沒完,機智的你會發現你調用函數的時候沒有傳值,下面我們來說一下傳值的問題,學習函數我們知道對形參傳值的時候如果你不確定你到底要傳多少值的時候我們可以這樣傳。

1 def func(*args,**kwargs):
2     
3     print(這是一個萬能的傳值方式)
4 
5 func()

  所以我們的裝飾器有變了個樣??

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     def inner(*args,**kwargs):
 5         start = time.time()
 6         ret = func(*args,**kwargs)
 7         end = time.time()
 8         print(end - start)
 9         return ret
10     return inner
11 @timmer
12 def func1(a,b):
13     time.sleep(0.1)
14     print(func1 over and get {} and {}.format(a,b))
15 func1(1,[a,b,c])
被裝飾的函數帶返回值

  此時的裝飾器已經堪稱完美,但是由於原函數被裝飾的原因我們不能查看原函數的“註釋”等內容,如下:

技術分享圖片
 1 import time
 2 
 3 def timmer(func):
 4     def inner(*args,**kwargs):
 5         start = time.time()
 6         ret = func(*args,**kwargs)
 7         end = time.time()
 8         print(end - start)
 9         return ret
10     return inner
11 @timmer
12 def func1(a,b):
13     """返回a,b的值"""
14     time.sleep(0.1)
15     print(func over and get {} and {}.format(a,b))
16     print(func1.__doc__)
17     print(func1.__name__)
18 func1(1,[a,b,c])
19 
20 #運行結果: func over and get 1 and [‘a‘, ‘b‘, ‘c‘]
21 #           None
22 #           inner
23 #           0.10055851936340332
不能查看原函數的方法

  所以你有對裝飾器函數進行了改裝。??

技術分享圖片
 1 from functools import wraps
 2 import time
 3 
 4 def timmer(func):
 5     @wraps(func)
 6     def inner(*args,**kwargs):
 7         start = time.time()
 8         ret = func(*args,**kwargs)
 9         end = time.time()
10         print(end - start)
11         return ret
12     return inner
13 @timmer
14 def func1(a,b):
15     """返回a,b的值"""
16     time.sleep(0.1)
17     print(func over and get {} and {}.format(a,b))
18     print(func1.__doc__)
19     print(func1.__name__)
20 func1(1,[a,b,c])
21 
22 #運行結果:func over and get 1 and [‘a‘, ‘b‘, ‘c‘]
23 #          返回a,b的值
24 #          func1
25 #          0.10077428817749023
改裝的裝飾器

  這樣裝飾器基本搞定了。下面再來對裝飾器作一些說明。

四、裝飾器的再次總結

  1.裝飾器是一個在不改變原函數的調用方式的情況下對函數進行拓展;

  2.裝飾器的本質是一個閉包函數;

  3.裝飾器完美的詮釋了編程開發中六大原則之一的“開放封閉原則”

  4.開放封閉原則:

    1.1 對擴展是開放的

    為什麽要對擴展開放呢?

    我們說,任何一個程序,不可能在設計之初就已經想好了所有的功能並且未來不做任何更新和修改。所以我們必須允許代碼擴展、添加新功能。

    2.1 對修改是封閉的

    為什麽要對修改封閉呢?

    就像我們剛剛提到的,因為我們寫的一個函數,很有可能已經交付給其他人使用了,如果這個時候我們對其進行了修改,很有可能影響其他已經在使用該函數的用戶。

  5. 裝飾器的固定模式

技術分享圖片
 1 from functools import wraps
 2 
 3 def timmer(func):
 4     @wraps(func)
 5     def inner(*args,**kwargs):
 6         """在被裝飾函數之前添加的功能"""
 7         ret = func(*args,**kwargs)
 8         """在被裝飾函數之後添加的功能"""
 9         return ret
10     return inner
11 @timmer
12 def func1(a,b):
13     """返回a,b的值"""
14     print(func over and get {} and {}.format(a,b))
15     
16 func1(1,[a,b,c])
裝飾器的固定模式

五、裝飾器的拓展
  1.帶參數的裝飾器

    為了年終獎你費氣八力的搞個裝飾器蒙混了老板,得到了20個月的年終獎金,那麽問題又來了!

    假如你有成千上萬個函數使用了一個裝飾器,現在你想把這些裝飾器都取消掉,你要怎麽做?一個一個的取消掉? 沒日沒夜忙活3天。。。過兩天你領導想通了,再讓你加上。。。

    崩潰中又帶著點慶幸,萬一又有獎金呢。繼續一頓搗鼓。。。

技術分享圖片
 1 from functools import wraps
 2 
 3 def hahaha(flag):
 4     def timmer(func):
 5         @wraps(func)
 6         def inner(*args,**kwargs):
 7             if flag:
 8                 """在被裝飾函數之前添加的功能"""
 9                 print(**********)
10             ret = func(*args,**kwargs)
11             if flag:
12                 """在被裝飾函數之後添加的功能"""
13                 print(##########)
14             return ret
15         return inner
16     return timmer
17 @hahaha(True)
18 def func1():
19     print(哈哈哈)
20 
21 func1()
22 
23 #運行結果:**********
24 #          哈哈哈
25 #          ##########
26 
27 from functools import wraps
28 
29 def hahaha(flag):
30     def timmer(func):
31         @wraps(func)
32         def inner(*args,**kwargs):
33             if flag:
34                 """在被裝飾函數之前添加的功能"""
35                 print(**********)
36             ret = func(*args,**kwargs)
37             if flag:
38                 """在被裝飾函數之後添加的功能"""
39                 print(##########)
40             return ret
41         return inner
42     return timmer
43 @hahaha(False)
44 def func1():
45     print(哈哈哈)
46 
47 func1()
48 
49 # 運行結果:哈哈哈
如何撤銷裝飾器函數

  不知不覺你又完成了一個強大的功能。只要在語法糖的地方稍稍改一下flag的布爾值就行了。哈哈哈。。。

  *******************註意:裝飾器最多只能定義三層嵌套函數,這已經是它的極限了************************

  2. 多個裝飾器裝飾一個函數  

    在實際的開發項目中,我們還會遇見多個裝飾器函數裝飾一個函數的情況,請看下面??

技術分享圖片
 1 from functools import wraps
 2 
 3 def wrapper1(func):
 4     def qqxing(*args,**kwargs):
 5         print(qqxing)
 6         ret1 = func(*args,**kwargs)
 7         print(shuangwaiwai)
 8         return ret1
 9     return qqxing
10 
11 def wrapper2(func):
12     def wahaha(*args,**kwargs):
13         print(wahaha)
14         ret2 = func(*args,**kwargs)
15         print(**************)
16         return ret2
17     return wahaha
18 @wrapper2            # func = wrapper2(func)
19 @wrapper1            # func = wrapper1(func)
20 def func():
21     print(I am a veryvery handsome boy)
22 
23 func()
24 
25 # 運行結果: wahaha
26 #           qqxing
27 #           I am a veryvery handsome boy
28 #           shuangwaiwai
29 #           **************
多個裝飾器裝飾一個函數

  如果你仔細看的話,你會發現在裝飾器函數中你添加的功能在打印的時候其順序是關於被裝飾的函數的輸出內容對稱的。是不是和俄羅斯套娃非常相像(雖然我都不知道俄羅斯套娃),如果你把打印的內容改一下就可以更加清楚的觀察了,但是我就不改,要改你自己改去。反正寫到這兒我感覺好累。不過註意一點,這個程序的執行過程還是有點復雜的,請看下面這張圖,我不確定能不能正確的畫出來,因為我累╯︿╰

技術分享圖片

  好了,執行過程大概就這樣,所以你要明白,想要別人方便你就得付出更多,但能夠作為一個python開發工作人員我樂意,我自豪,我有成就感。    

  這是今天的裝飾器函數,若還有什麽地方忘記寫的以後會補上。

  以上內容參考了Eval_J老師的python之路——裝飾器函數,有興趣的朋友們可以去看一下。She is a very pretty girl。鏈接 ?? https://www.cnblogs.com/Eva-J/articles/7194277.html#3999110

  

python學習——裝飾器函數