Python 装饰器初探
Python 裝飾器初探
在談及Python的時候,裝飾器一直就是道繞不過去的坎。面試的時候,也經常會被問及裝飾器的相關知識。總感覺自己的理解很淺顯,不夠深刻。是時候做出改變,對Python的裝飾器做個全面的了解了。
1. 函數裝飾器
直接上代碼,看看裝飾器到底干了些什么?
from functools import wraps import timedef 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的語言特性,也能達成上述目的。用裝飾器符號的好處是簡化了代碼,增加了代碼的可讀性。
這是一段非常簡單的對函數使用裝飾器的Python代碼。等等,@wraps(func)是什么鬼?悄悄干了什么哇?
我們稍微修改下上述代碼,結果如下:
from functools import wraps import timedef 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。
下面介紹帶參數的裝飾器,更加難了。在談論帶參數的裝飾器之間,首先得引入一個概念,那就”閉包“。如果你以前用過腳本語言,比如JavaScript,那么一定會很熟悉閉包這個概念。下面是一個閉包樣例
def add(a):def wrapper(c):return a + creturn wrapperif __name__ == "__main__":add3 = add(3)add9 = add(9)print(add3(4) == 7)print(add9(1) == 10)從中可以看出,在調用add3的時候,wrapper內部還可以訪問到a的值,這就是閉包的作用。理解了閉包,理解帶參數的裝飾器就容易多了。
from functools import wrapsdef 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_wrapperreturn 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)()。注意與不帶參數的裝飾器的區別,帶參數的裝飾器比不帶參數的裝飾器多套了一層,對應的裝飾器也有了調用。因為在使用裝飾器的時候,帶了括號,所以裝飾器本身多套了一層。被裝飾器修飾過的函數在被調用的時候,實際上執行的是裝飾器最內層的函數,其余層的在函數被修飾時就已經執行了。
是不是覺得非常自然?對的,我以前對裝飾器的理解也就停留在不帶參數的裝飾器這一深度。
2. 基于類實現的裝飾器
依然先上代碼
from functools import wraps import timeclass time_cost:def __init__(self, func):self.func = funcdef __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 wrapsclass logging:def __init__(self, level):self.level = leveldef __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方法。
現在看來,其實裝飾器也沒有很復雜,在實際的項目中用裝飾器可以帶來很大便利。
轉載于:https://www.cnblogs.com/crackpotisback/p/10197698.html
總結
以上是生活随笔為你收集整理的Python 装饰器初探的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux内核开发_Linux 内核的代
- 下一篇: python 线性回归回归 缺失值 忽略