python中的裝飾器是一個用得非常多的東西,我們可以把一些特定的方法、通用的方法寫成一個個裝飾器,這就為調用這些方法提供一個非常大的便利,如此提高我們代碼的可讀性以及簡潔性,以及可擴充性。
在學習python裝飾器之前我們先看看這樣一個例子:
一、範圍
# coding:utf-8 msg = 'hello test1' def add(): msg = 'this is add' print msg #當執行add()時將列印'this is add'def add2(): print msg #當執行add2()時將列印'hello test1'
上面的例子簡單地對python的範圍做了一個說明,例子中申明了全域變數msg,add函數中也申明了一個局部變數msg,當執行add()的 "print msg" 時會先找add裡面是否有局部變數msg,如果沒找到就去上一級範圍找是否存在該變數,局部變數在函數運行時產生,當函數結束運行時局部變數也隨之銷毀,接下來我們對上面的例子加深:
二、閉包
# coding:utf-8 def add(): msg = 'hello add' def inner(): print msg #運行add()這裡會列印'hello add' return inner
>>> obj = add()>>> obj #可見obj是一個指向inner函數的對象<function inner at 0x0000000002855438>...>>> obj()hello add #運行obj()列印msg
看了上面的例子有沒有一點疑惑,obj是指向inner函數的對象,當運行obj時就等同於運行inner,但是add函數並沒有運行,也就是說msg沒有被申明同時inner也沒有申明變數msg,它又是如何找到msg這個變數的值呢?
這就是python裡的"閉包",Python支援一個叫做函數閉包的特性,用人話來講就是,嵌套定義在非全域範圍裡面的函數能夠記住它在被定義的時候它所處的封閉命名空間。這能夠通過查看函數的obj.func_closure屬性得出結論,這個屬性裡麵包含封閉範圍裡面的值(只會包含被捕捉到的值,如果在add裡面還定義了其他的值,封閉範圍裡面是不會的)閉包就是python裝飾器的核心原理,接下來我們寫一個簡單的裝飾器例子:
三、簡單的裝飾器
# coding:utf-8 def check_args_num(func): # 該裝飾器用於檢查傳入的參數數量,檢查是否是兩個參數 def inner(*args, **kwargs): args_list = list(args) if len(args_list) < 2: for i in range(2 - len(args)): # 如果參數數量小於2則用0填補 args_list.append(0) if len(args_list) > 2: # 如果參數數量大於2則列印錯誤 print 'The args number is too many!' func(*args_list, **kwargs) return inner @check_args_numdef add(x, y): return x + y
執行結果:
>>>print add(1,2)3...>>>print add(100)100...>>>print add(1,2,3)Traceback (most recent call last): File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec exec exp in global_vars, local_vars File "<input>", line 1, in <module> File "E:/code/my_project/decorator/test1.py", line 14, in inner raise Exception('The args number is too many!')Exception: The args number is too many!...>>>add<function inner at 0x0000000002A6C3C8>#可以看到add函數現在指向的是inner
四、多個裝飾器
# coding:utf-8 def check_args_int(func): # 該裝飾器用於檢查傳入的參數是否是int型 def ensure_int(*args, **kwargs): from array import array try: array('H', args) except Exception, e: raise Exception(e) return func(*args, **kwargs) return ensure_int def check_args_num(func): # 該裝飾器用於檢查傳入的參數數量,檢查是否是兩個參數 def inner(*args, **kwargs): args_list = list(args) if len(args_list) < 2: for i in range(2 - len(args)): # 如果參數數量小於2則用0填補 args_list.append(0) if len(args_list) > 2: # 如果參數數量大於2則列印錯誤 raise Exception('The args number is too many!') return func(*args_list, **kwargs) return inner @check_args_num@check_args_intdef add(x, y): return x + y
這裡多加了一個參數內容檢查的裝飾器,當多個裝飾器時,會自上而下執行,先執行check_args_num再執行check_args_int,執行結果:
>>> print add(1,'fsaf')Traceback (most recent call last): File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec exec exp in global_vars, local_vars File "<input>", line 1, in <module> File "E:/code/my_project/decorator/test1.py", line 28, in inner return func(*args_list, **kwargs) File "E:/code/my_project/decorator/test1.py", line 10, in ensure_int raise Exception(e)Exception: an integer is required...>>> add<function inner at 0x0000000002B1C4A8>
#這裡可以看到add還是指向inner,我們可以這樣理解當多個裝飾器存在時,當調用add時,其調用入口始終是第一個裝飾器,第一個裝飾器執行完,再執行下一個,同時也會將參數依次傳遞下去
五、帶參數的裝飾器
我們知道定義裝飾器的時候傳入裝飾器的第一個參數是被裝飾的函數(eg.例子中的add),有些時候我們也需要給裝飾器傳遞額外的參數,下面例子將給裝飾器傳遞額外參數,如下:
# coding:utf-8 def check_args_int(func): # 該裝飾器用於檢查傳入的參數是否是int型 def ensure_int(*args, **kwargs): from array import array try: array('H', args) except Exception, e: raise Exception(e) return func(*args, **kwargs) return ensure_int def check_args_num(flag): ''' :param func: 被裝飾函數 :param flag: 決定是否檢查參數數量 ''' # 該裝飾器用於檢查傳入的參數數量,檢查是否是兩個參數 def get_func(func): def inner(*args, **kwargs): if flag == 'false': print 'Skip check !' return func(*args, **kwargs) args_list = list(args) if len(args_list) < 2: for i in range(2 - len(args)): # 如果參數數量小於2則用0填補 args_list.append(0) if len(args_list) > 2: # 如果參數數量大於2則列印錯誤 raise Exception('The args number is too many!') return func(*args_list, **kwargs) return inner return get_func @check_args_num('false')@check_args_intdef add(x, y): return x + y
這個例子只有check_args_num和之前的有所不同,不同在於裝飾器check_args_num多了一個參數flag,當flag=='false'時,跳過參數數量檢查,下面是輸出結果
>>>print add(1, 2)Skip check !3
六、decorator不帶參數的裝飾器
decorator模組是python用來專門封裝裝飾器的模組,使用decorator構造裝飾器更加簡便,同時被裝飾的函數簽名也保留不變
之前的講了通過閉包實現python裝飾器構造,這裡利用decorator模組實現python裝飾器與其原理都是一樣的
from decorator import decorator @decoratordef check_num(func, *args, **kwargs): if len(args) != 2: raise Exception('Your args number is not two') else: return func(*args, **kwargs) @check_numdef add(x, y): return x + y
>>> add<function add at 0x0000000002D43BA8>>>> print add(1,2)3...>>> add(1,2,3)Traceback (most recent call last): File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec exec exp in global_vars, local_vars File "<input>", line 1, in <module>TypeError: add() takes exactly 2 arguments (3 given) #可以看到這裡當我們傳三個參數給add()函數時,他直接從add()函數拋出類型錯誤異常,#並沒有進入check_num裝飾器進行參數校正,可見被裝飾的函數add()的簽名還是他本身#如果直接構造裝飾器,那麼這裡將會從check_num裡面拋出異常,如下: def check_num(func): def inner(*args,**kwargs): if len(args) != 2: raise Exception('Your args number is not two') else: return func(*args,**kwargs) return inner @check_numdef add(x, y): return x + y >>> add<function inner at 0x0000000002E233C8>>>>add(1,2,3)Traceback (most recent call last): File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 2411, in <module> globals = debugger.run(setup['file'], None, None, is_module) File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 1802, in run launch(file, globals, locals) # execute the script File "E:/code/my_project/decorator/test3.py", line 14, in <module> print add(1,2,3) File "E:/code/my_project/decorator/test3.py", line 4, in inner raise Exception('Your args number is not two')Exception: Your args number is not two
從上面的例子可以看出,通過閉包的方式構造裝飾器時,其執行函數入口是裝飾器中的嵌套函數,這樣就可能出現上面出現的問題,當add(1,2,3)執行時會先執行inner函數(如果inner裡面沒有參數校正則這裡是不會拋出異常的,只有當執行到return func(*args,**kwargs)時才真正調用add(x,y)函數,這時候才會拋出異常)這樣就會造成程式執行多餘的代碼,浪費記憶體,cpu。
七、decorator帶參數的裝飾器
如果想讓裝飾器帶參數呢?
from decorator import decorator def check(flag): @decorator def check_num(func, *args, **kwargs): if flag == 'false': print 'skip check !' return func(*args,**kwargs) if len(args) != 2: raise Exception('Your args number is not two') else: return func(*args, **kwargs) return check_num @check('false')def add(x, y): return x + y >>>add<function add at 0x0000000002C53C18>>>>add(1,2)skip check !3
decorator 模組就講到這裡,這個模組比較簡單,還有一些功能沒講到的查看源碼便一目瞭然,其原理都是利用python閉包實現的
八、functools.wraps(func)裝飾器
functools.wraps和decorator模組的功能是一樣的,都是為瞭解決被裝飾函數的簽名問題,這裡只對該種裝飾器構造方法列舉一個帶參數例子:
import functools def check(flag): def wraps(func): @functools.wraps(func) def check_num(*args, **kwargs): if flag == 'false': print 'skip check !' return func(*args,**kwargs) if len(args) != 2: raise Exception('Your args number is not two') else: return func(*args, **kwargs) return check_num return wraps @check('false')def add(x, y): return x + y
對比上面通過decorator模組裝飾函數的例子,我們可以發現,用decorator裝飾函數的代碼更加簡潔易懂,但是他們二者的執行效率誰更高呢?下面我們通過Timer來測試下:
from timeit import Timer print Timer('add(1,2)',setup='from __main__ import add').timeit(100000) #將該段代碼 加在 之前的例子中#這裡列印的是運行100000次的時間
functools.wraps裝飾執行結果:
2.37299322602
decorator模組裝飾執行結果:
3.42141566059
執行效率wraps略高,但是這裡是執行了10萬次他們之間的差距約為1秒,所以我個人還是比較青睞於用decorator模組裝飾函數,畢竟看起來易懂,寫法也較為簡單!本文就將裝飾器介紹到這裡了,當然也沒有說盡裝飾器的妙用,比如:裝飾類...其原理是用類來當做裝飾器,類裡面需要用到__call__方法,至於裝飾類的用法感興趣的朋友自行百度咯!