如何理解Python裝飾器?不少學員對此都有疑問,那麼上海尚學堂python培訓這篇文章就給予答覆。python
首先要理解裝飾器,首先要先理解在 Python 中很重要的一個概念就是:「函數是 First Class Member」 。這句話再翻譯一下,函數是一種特殊類型的變量,能夠和其他變量同樣,做爲參數傳遞給函數,也能夠做爲返回值返回,上海python培訓。函數
def abc(): print("abc") def abc1(func): func() abc1(abc)
這段代碼的輸出就是咱們在函數 abc 中輸出的 abc 字符串。過程很簡單,咱們將函數 abc 做爲一個參數傳遞給 abc1 ,而後,在 abc1 中調用傳入的函數
再來看一段代碼oop
def abc1(): def abc(): print("abc") return abc abc1()()
這段代碼輸出和以前的同樣,這裏咱們將在 abc1 內部定義的函數 abc 做爲一個變量返回,而後咱們在調用 abc1 獲取到返回值後,繼續調用返回的函數。
好了,咱們再來作一個思考題,實現一個函數 add ,達到 add(m)(n) 等價於 m+n 的效果。這題若是把以前的 First-Class Member 這一律念理清楚後,咱們便能很清楚的寫出來了學習
def add(m): def temp(n): return m+n return temp print(add(1)(2))
嗯,這裏輸出就是 3 。翻譯
看了前面的預備知識後,咱們即可以開始今天的主題了code
如今咱們有一個函數路由
def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result
如今咱們要給這個函數加上一些代碼,來計算這個函數的運行時間。
咱們大概一想,寫出了這樣的代碼字符串
import time def range_loop(a,b): time_flag=time.time() for i in range(a,b): temp_result=a+b print(time.time()-time_flag) return temp_result
先且不論,這樣計算時間是否是準確的,如今咱們要給以下不少函數加上一個時間計算的功能input
import time def range_loop(a,b): time_flag=time.time() for i in range(a,b): temp_result=a+b print(time.time()-time_flag) return temp_result def range_loop1(a,b): time_flag=time.time() for i in range(a,b): temp_result=a+b print(time.time()-time_flag) return temp_result def range_loop2(a,b): time_flag=time.time() for i in range(a,b): temp_result=a+b print(time.time()-time_flag) return temp_result
咱們初略一想,嗯,Ctrl+C,Ctrl+V。emmmm 好了,如今大家不以爲這段代碼特別髒麼?咱們想讓他變得乾淨點怎麼辦?
咱們想了想,按照以前說的 First-Class Member 的概念。而後寫出了以下的代碼學習資料
import time def time_count(func,a,b): time_flag=time.time() temp_result=func(a,b) print(time.time()-time_flag) return temp_result def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop1(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop2(a,b): for i in range(a,b): temp_result=a+b return temp_result time_count(range_loop,a,b) time_count(range_loop1,a,b) time_count(range_loop2,a,b)
嗯,看起來像那麼回事,好了好了,咱們如今新的問題又來了,咱們如今是假設,咱們全部函數都只有兩個參數傳入,那麼如今若是想支持任意參數的傳入怎麼辦?咱們眉頭一皺,寫下了以下的代碼
import time def time_count(func,*args,**kwargs): time_flag=time.time() temp_result=func(*args,**kwargs) print(time.time()-time_flag) return temp_result def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop1(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop2(a,b): for i in range(a,b): temp_result=a+b return temp_result time_count(range_loop,a,b) time_count(range_loop1,a,b) time_count(range_loop2,a,b)
好了,如今看起來,有點像模像樣了,可是咱們再想一想,這段代碼實際上改變了咱們的函數調用方式,好比咱們直接運行 range_loop(a,b) 仍是沒有辦法獲取到函數執行時間。那麼如今咱們若是不想改變函數的調用方式,又想獲取到函數的運行時間怎麼辦?
很簡單嘛,替換一下不就行了
import time def time_count(func): def wrap(*args,**kwargs): time_flag=time.time() temp_result=func(*args,**kwargs) print(time.time()-time_flag) return temp_result return wrap def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop1(a,b): for i in range(a,b): temp_result=a+b return temp_result def range_loop2(a,b): for i in range(a,b): temp_result=a+b return temp_result range_loop=time_count(range_loop) range_loop1=time_count(range_loop1) range_loop2=time_count(range_loop2) range_loop(1,2) range_loop1(1,2) range_loop2(1,2)
emmmm,這樣看起來感受舒服多了?既沒有改變原有的運行方式,也輸出了函數運行時間。
可是。。。大家不以爲手動替換太噁心了麼???喵喵喵???還有什麼能夠簡化下的麼??
好了,Python 知道咱們是愛吃糖的孩子,給咱們提供了一個新的語法糖,這也是今天的男一號,Decorator 裝飾器
咱們前面已經實現了,在不改變函數特性的狀況下,給原有的代碼新增一點功能,可是咱們也以爲這樣手動的替換,太噁心了,是的 Python 官方也以爲這樣很噁心,因此新的語法糖來了
咱們上面的代碼能夠寫成這樣了
import time def time_count(func): def wrap(*args,**kwargs): time_flag=time.time() temp_result=func(*args,**kwargs) print(time.time()-time_flag) return temp_result return wrap @time_count def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result @time_count def range_loop1(a,b): for i in range(a,b): temp_result=a+b return temp_result @time_count def range_loop2(a,b): for i in range(a,b): temp_result=a+b return temp_result range_loop(1,2) range_loop1(1,2) range_loop2(1,2)
哇,寫到這裏,你是否是恍然大悟!まさか???是的,其實 @ 符號實際上是一個語法糖,他將咱們以前的手動替換的過程交給了環境執行。好了用人話描述下,@ 的做用是將被包裹的函數做爲一個變量傳遞給裝飾函數/類,將裝飾函數/類返回的值替換本來的函數。
@decorator
def abc():
pass
如同前面所講的同樣,其實是發生了一個特殊的替換過程 abc=decorator(abc) ,好了咱們來作幾個題來練習下吧?
def decorator(func): return 1 @decorator def abc(): pass abc()
這段代碼會發生什麼?答:會拋出異常。爲啥啊?答:由於裝飾的時候發生了替換,abc=decorator(abc) ,替換後 abc 的值爲 1 。整數默認不能做爲一個函數進行調用。
def time_count(func): def wrap(*args,**kwargs): time_flag=time.time() temp_result=func(*args,**kwargs) print(time.time()-time_flag) return temp_result return wrap def decorator(func): def wrap(*args,**kwargs): temp_result=func(*args,**kwargs) return temp_result return wrap def decorator1(func): def wrap(*args,**kwargs): temp_result=func(*args,**kwargs) return temp_result return wrap @time_count @decorator @decorator1 def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result
這段代碼怎麼替換的?答:time_count(decorator(decorator1(range_loop)))
嗯,如今是否是對裝飾器什麼的有了基本的瞭解?
如今,我想修改下前面寫的 time_count 函數,讓他支持傳入一個 flag 參數,當 flag 爲 True 的時候,輸出函數運行時間,爲 False 的時候不輸出時間
咱們一步步來,咱們先假設新的函數叫作 time_count_plus
咱們想實現的效果是這樣的
@time_count_plus(flag=True) def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result
嗯,咱們看了下,首先咱們調用了 time_count_plus(flag=True) 一次,將它返回的值做爲一個裝飾函數來替換 range_loop ,OK 那麼咱們首先 time_count_plus 要接收一個參數,返回一個函數對吧
def time_count_plus(flag=True): def wrap1(func): pass return wrap1
好了,如今返回了一個函數來做爲裝飾函數,而後咱們說了 @ 其實觸發了一次替換過程,好那麼咱們如今的替換是否是 range_loop=time_count_plus(flag=True)(range_loop) 好了,如今你們應該很清楚了,咱們在 wrap1 裏面是否是還應該有一個函數並返回?
嗯,最終的代碼以下
def time_count_plus(flag=True): def wrap1(func): def wrap2(*args,**kwargs): if flag: time_flag=time.time() temp_result=func(*args,**kwargs) print(time.time()-time_flag) else: temp_result=func(*args,**kwargs) return temp_result return wrap2 return wrap1 @time_count_plus(flag=True) def range_loop(a,b): for i in range(a,b): temp_result=a+b return temp_result
是否是這樣就清楚多啦!
好了,咱們如今有新的需求來了
m=3 n=2 def add(a,b): return a+b def sub(a,b): return a-b def mul(a,b): return a*b def div(a,b): return a/b
如今咱們有字符串 a , a 的值可能爲 +、-、*、/ 那麼如今,咱們想根據 a 的值來調用對應的函數怎麼辦?
咱們煎蛋一想,嗯,邏輯判斷嘛
m=3 n=2 def add(a,b): return a+b def sub(a,b): return a-b def mul(a,b): return a*b def div(a,b): return a/b a=input('請輸入 + - * / 中的任意一個\n') if a=='+': print(add(m,n)) elif a=='-': print(sub(m-n)) elif a=='*': print(mul(m,n)) elif a=='/': print(div(m,n))
可是這段代碼,if else 是否是太多了點?咱們仔細一想,用一下 First-Class Member 的特性,而後配合 dict 實現操做符和函數之間的關聯。
m=3 n=2 def add(a,b): return a+b def sub(a,b): return a-b def mul(a,b): return a*b def div(a,b): return a/b func_dict={"+":add,"-":sub,"*":mul,"/":div} a=input('請輸入 + - * / 中的任意一個\n') func_dict[a](m,n)
emmmm,看起來不錯啊,可是咱們註冊的過程能不能再簡化一點? 嗯,這個時候裝飾器語法特性就能用上了
m=3 n=2 func_dict={} def register(operator): def wrap(func): func_dict[operator]=func return func return wrap @register(operator="+") def add(a,b): return a+b @register(operator="-") def sub(a,b): return a-b @register(operator="*") def mul(a,b): return a*b @register(operator="/") def div(a,b): return a/b a=input('請輸入 + - * / 中的任意一個\n') func_dict[a](m,n)
嗯,還記得咱們前面說的使用 @ 語法的時候,其實是觸發了一個替換的過程麼?這裏就是利用這一特性,在裝飾器觸發的時候,註冊函數映射,這樣咱們直接根據 'a' 的值來獲取函數處理數據。另外請注意一點,咱們這裏沒有必要修改原函數,因此咱們沒有必要寫第三層的函數。
若是有熟悉 Flask 同窗就知道,在調用 route 方法註冊路由的時候,也是使用了這一特性 。
其實全文下來,你們應該能知道這樣一點東西。Python 中的裝飾器實際上是 First-Class Member 概念的更進一層應用,咱們將函數傳遞給其他函數,包裹上新的功能後再行返回。@ 其實只是將這樣一個過程進行了簡化而已。
感謝您閱讀,歡迎評論,更多文章或獲取python學習資料請點擊參看:上海python培訓