在談及Python的時候,裝飾器一直就是道繞不過去的坎。面試的時候,也常常會被問及裝飾器的相關知識。總感受本身的理解很淺顯,不夠深入。是時候作出改變,對Python的裝飾器作個全面的瞭解了。python
直接上代碼,看看裝飾器到底幹了些什麼?面試
from functools import wraps import time def time_cost(func): @wraps(func) def f(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print(end_time - start_time) return f @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": test()
上面的Python代碼,運行後,會給出test函數的執行時間。代碼的執行順序大概以下,首先是將test做爲值傳遞給time_cost函數,返回函數f,而後再調用f,這是帶有time_cost裝飾器的test函數的大體執行過程。閉包
從中,不難看出,即便不使用裝飾器符號,咱們利用Python的語言特性,也能達成上述目的。用裝飾器符號的好處是簡化了代碼,增長了代碼的可讀性。app
這是一段很是簡單的對函數使用裝飾器的Python代碼。等等,@wraps(func)
是什麼鬼?悄悄幹了什麼哇?函數
咱們稍微修改下上述代碼,結果以下:code
from functools import wraps import time def time_cost(func): def f(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print(end_time - start_time) print('hello world') return f @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": print(test.__name__)
發現輸出了hello world
,同時輸出test.__name__
,竟然變成了f
,並非咱們預期的test
。根據這樣的輸出結果,咱們不可貴出,其實被裝飾器time_cost
修飾過的函數test本質上已經等同於time_cost(test)
,此時訪問test.__name__
實際上訪問的是time_cost(test).__name__
,獲得的固然就是f
啦。當咱們加上@wraps(func)
,此時test.__name__
變成了test
。orm
下面介紹帶參數的裝飾器,更加難了。在談論帶參數的裝飾器之間,首先得引入一個概念,那就」閉包「。若是你之前用過腳本語言,好比JavaScript,那麼必定會很熟悉閉包這個概念。下面是一個閉包樣例對象
def add(a): def wrapper(c): return a + c return wrapper if __name__ == "__main__": add3 = add(3) add9 = add(9) print(add3(4) == 7) print(add9(1) == 10)
從中能夠看出,在調用add3的時候,wrapper內部還能夠訪問到a的值,這就是閉包的做用。理解了閉包,理解帶參數的裝飾器就容易多了。ip
from functools import wraps def logging(level): def outer_wrapper(func): @wraps(func) def inner_wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return outer_wrapper @logging(level='WARN') def show(msg): print('message:{}'.format(msg)) if __name__ == "__main__": show('hello world!')
上面給出了一個帶參數裝飾器的示例。根據咱們前面的鋪墊,咱們不難分析得出,上面的執行過程是logging(level='WARN')->outer_wrapper(show)->inner_wrapper()
,因此咱們能夠理解,在被logging修飾後的show其實就是logging(level='WARN')(show)
,執行show('hello world!')
其實就是在執行logging(level='WARN')(show)()
。注意與不帶參數的裝飾器的區別,帶參數的裝飾器比不帶參數的裝飾器多套了一層,對應的裝飾器也有了調用。由於在使用裝飾器的時候,帶了括號,因此裝飾器自己多套了一層。被裝飾器修飾過的函數在被調用的時候,實際上執行的是裝飾器最內層的函數,其他層的在函數被修飾時就已經執行了。it
是否是以爲很是天然?對的,我之前對裝飾器的理解也就停留在不帶參數的裝飾器這一深度。
依然先上代碼
from functools import wraps import time class time_cost: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): start_time = time.time() result = self.func(*args, **kwargs) end_time = time.time() print(end_time - start_time) return result @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": test()
上面的基於類實現的不帶參數的裝飾器實際上利用的是Python中的可調用對象特性,凡是實現了__call__
方法的類的實例是能夠被調用的。所以被time_cost
修飾過的test
函數本質上已經變成了time_cost類的實例了。調用test方法的時候,實際上執行的是__call__
方法。
下面介紹稍微複雜一點的基於類實現的帶有參數的裝飾器。
from functools import wraps class logging: def __init__(self, level): self.level = level def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=self.level, func=func.__name__)) return func(*args, **kwargs) return wrapper @logging(level='WARN') def show(msg): print('message:{}'.format(msg)) if __name__ == "__main__": show('hello world!')
不一樣於基於類實現的不帶參數的裝飾器,基於類實現的帶參數的裝飾器在__call__
裏面多了一層wrapper。被裝飾器修飾的show方法本質上是logging(level='WARN')(show)
,此時調用show方法,實際上執行的是wrapper方法。
如今看來,其實裝飾器也沒有很複雜,在實際的項目中用裝飾器能夠帶來很大便利。