Python裝飾器詳解
# 定義一個函式 def test(number_out): # 在函式內部再定義一個函式,並且這個函式用到了外邊函式的變數,那麼將這個內部函式以及用到的一些變數稱之為閉包 def test_in(number_in): print("number_out:%d" % number_out) print("in test_in 函式, number_in is %d" % number_in) return number_out+number_in # 其實這裡返回的就是閉包的結果 return test_in # 給test函式賦值,這個20就是給引數number_out ret = test(20) # 注意這裡的100其實給引數number_in print(ret(100))
執行結果:
閉包:即兩個函式巢狀,外部函式返回內部函式的引用,外部函式一定會傳入引數,外部函式起的是交換引用的作用.
閉包從語法上看非常簡單,但是卻有強大的作用。 閉包可以將其自己的程式碼和作用域以及外部函式的作用結合在一起。
總結:什麼函式可以被稱為閉包函式呢?主要是滿足兩點:函式內部定義的函式;引用了外部變數但非全域性變數。
2..什麼是裝飾器?
有了閉包函式的概念,我們再去理解裝飾器會相對容易一些。 python裝飾器本質上就是一個函式,它可以讓其他函式在不需要做任何程式碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函式物件。 裝飾器函式的外部函式傳入我要裝飾的函式名字,返回經過修飾後函式的名字;內層函式(閉包)負責修飾被修飾函式。從上面這段描述中我們需要記住裝飾器的幾點屬性,以便後面能更好的理解:
實質: 是一個函式
引數:是你要裝飾的函式名(並非函式呼叫)
返回:是裝飾完的函式名(也非函式呼叫)
作用:為已經存在的物件新增額外的功能
特點:不需要對物件做任何的程式碼上的變動
3.裝飾器的作用
裝飾器的作用: python裝飾器就是用於拓展原來函式功能的一種函式,這個函式的特殊之處在於它的返回值也是一個函式,使用python裝飾器的好處就是在不用更改原函式的程式碼前提下給函式增加新的功能。
一般而言,我們要想拓展原來函式程式碼,最直接的辦法就是侵入程式碼裡面修改,但這種方法有弊端,因為修改原始碼不能保證原始碼的其他模組功能的有效性,而使用裝飾器能完美解決這一問題。
python裝飾器有很多經典的應用場景,比如:插入日誌、效能測試、事務處理、許可權校驗等。裝飾器是解決這類問題的絕佳設計。並且從引入中的列子中我們也可以歸納出:裝飾器最大的作用就是對於我們已經寫好的程式,我們可以抽離出一些雷同的程式碼組建多個特定功能的裝飾器,這樣我們就可以針對不同的需求去使用特定的裝飾器,這時因為原始碼去除了大量泛化的內容而使得原始碼具有更加清晰的邏輯。
4.幾種常用裝飾器
4.1 函式的函式裝飾器
我們以函式新增計時功能為例,講述函式裝飾器。
import time def decorator(func): def wrapper(*args, **kwargs): start_time = time.time() func() end_time = time.time() print(end_time - start_time) return wrapper @decorator# @decorator 相當於 test = decorator(test) def test(): time.sleep(0.8) print("test原始碼") # 函式呼叫 test()
在上面程式碼中 test是我要裝飾器的函式,我想用裝飾器顯示test函式執行的時間。@decorator這個語法相當於 執行 test= decorator(test),為test函式裝飾並返回。
再來看一下我們的裝飾器函式 decorator,該函式的傳入引數是func (即被裝飾函式test的引用),返回引數是內層函式。這裡的內層函式wrapper,其實就相當於閉包函式,它起到裝飾給定函式的作用。
4.2 被裝飾的函式有不定長引數
import time def timefun(func): def wrapped_func(*args, **kwargs): print("%s called at %s" % (func.__name__, time.ctime())) func(*args, **kwargs) return wrapped_func @timefun def foo(a, b, c, d="foo原始碼"): time.sleep(0.8) print(a + b + c) print(d) foo(3, 5, 7)
執行結果:
wrapper引數為*args, **kwargs。 *args表示的引數以列表的形式傳入;**kwargs表示的引數以字典的形式傳入:
從圖中我們可以看到:凡是以key=value形式的引數均存在kwargs中,剩下的所有引數都以列表的形式存於args中。這裡要注意的是: 為了不破壞原函式的邏輯,我們要保證內層函式wrapper_func和被裝飾函式func的傳入引數和返回值型別必須保持一致。
4.3. 類裝飾器
前面我們提到的都是讓 函式作為裝飾器去裝飾其他的函式或者方法,那麼可不可以讓 一個類發揮裝飾器的作用呢?答案肯定是可以的,python中一切皆物件,函式和類本質沒有什麼不一樣。
class Decorator(object): def __init__(self, f): self.f = f def __call__(self): print("decorator start") self.f() print("decorator end") @Decorator def func(): print("func") func()
這裡值得注意的是:__call__()是一個特殊方法,它可將一個類例項變成一個可呼叫物件:
func = Decorator(func) # func是類Decorator的一個例項 func() # 實現了__call__()方法後,func可以被呼叫
要使用類裝飾器必須實現類中的__call__()方法,就相當於將例項變成了一個方法。