入門向python裝飾器理解

裝飾器

此爲個人學習筆記,內容僅供參考,轉載請註明作者,如有錯誤,歡迎指正。)

記得以前學到這裏,看了很多關於python裝飾器的文章,對於裝飾器還是一知半解,後來在一些實例中, 通過最樸實的方法總算是摸清楚了什麼是裝飾器?以下是個人對裝飾器的筆記。

什麼是裝飾器?

在學習裝飾器之前,我們必須搞清楚一點,裝飾器究竟是什麼? 用文字說明, 裝飾器實際上就是爲了給某個函數,或者稱某個程序添加一個功能。並且考慮 到該函數或程序已經被引用,所以我們不能對函數或程序本身進行修改,所以 有了裝飾器,可以想象成,我們人拿上斧頭,擁有了砍樹的能力,這就是裝飾器的作用。

裝飾器需要滿足以下兩點:

  1. 不修改原程序或函數的代碼
  2. 不改變函數或程序的調用方法

ps:此處如果不理解,我們還用人砍樹的例子。比如說小明他是個人,你要讓他砍樹,他怎麼砍, 必須要讓他擁有砍樹能力,那麼問題來了,怎麼讓他擁有這個能力呢?好吧可以給他注射藥劑, 讓他變強,但是這就不滿足上述第一點了,被改造後的小明我們就不認識他了,不知道怎麼讓他去砍樹, 所以就必須有前提條件1,不得修改源代碼。 第二條,不改變調用方法。原本把我想讓小明去砍樹就對 小明下命令,“嘿小明去砍個樹”,但是小明不會啊,怎麼辦呢,讓一個會的人帶着小明去,這個過程就成了什麼呢? 你讓某個人帶着小明去,而不是你讓小明去,這就出現了調用方法的改變。

實例中理解

第一步:裝飾器的前奏

定義一個測試的函數,返回輸入的值

def test1(a):
	return a

上述定義的函數,它的作用就是返回輸入值,那麼接下來要考慮給它加一個計算它自身運行時間的功能。最簡單的思路是,記錄開始時間->運行函數->記錄結束時間, 用結束時間減去開始時間就是我們想要的結果了,如下:

import time
start_time = time.time()	# 開始時間
test1(250)					# 要測試的函數
stop_time = time.time()		# 結束時間
delta_time = stop_time - start_time    # 時間間隔,即運行時間

好了,簡單地實現了。根據需求應該把這個過程封裝在函數內,調用一次就返回test1的運行時間。塞到函數裏面大概是這樣:

def test2()
	start_time = time.time()	# 開始時間
	test1(250)					# 要測試的函數
	stop_time = time.time()		# 結束時間
	delta_time = stop_time - start_time    # 時間間隔,即運行時間
	print('{:f}'.format(delta_time))  	   # 格式化打印

運行一下

>>> test2(250)
'0.000001'

此處可以看到,將上述過程封裝到 test2 之後,通過調用 test2 就能得到 test1 函數的運行時間, 但是不難發現有兩個點:
(1)改變了調用方法,根據裝飾器的描述,應該直接調用test1就得到運行時間
(2)此處只研究了test1(250)也就是說此時test1只有一個固定參數,這不能保證是否會因爲參數的不同運行時間不同。

針對這兩個點進一步改進

首先來解決一下關於調用的時候不再是固定參數,如下:

def test2(func):
	def test3(x):
		start_time = time.time()	# 開始時間
		func(x)						# 要測試的函數	
		stop_time = time.time()		# 結束時間
		delta_time = stop_time - start_time    # 時間間隔,即運行時間
		return '{:f}'.format(delta_time)  	   # f返回格式化時間
	return test3

def test1(a):
	return a

運行一下

>>> test2(test3)(250)
'0.000002'

現在有三個函數test1, test2, test3。整個流程還是得梳理清楚,是爲了計算test1的運行時間, 那麼還是"記錄開始時間->運行函數->記錄結束時間"這一個流程。

在上面這段代碼中,首先把 test1 的函數地址當成參數傳入到了 test2 , 此時 test2(func) 相當於 test2(test1),在 test2 中,再定義一個test3。把上述求運行時間的流程放進去,在test3中可以看到一個func(x)func是我們傳入的地址也就是test1,要計算test1的運行時間,那肯定是需要調用的, func() 就是 func 的調用,func(x) 相當於 test1(x)

再來看 test2 函數的返回值,是test3的地址,一方面不會因爲調用而報錯,只是拿了 test3 函數的地址; 一方面是爲了取得 test3 以保證後面的展開。現在目的就很明確了。調用test2, 傳入test1的地址,又因爲是返回了test3的地址,實際上經過一頓操作,test2(test1) 就像是 test3 的地址, test2(test1)() 就是在實例化 test3 了, 即 test3(), 顯而易見,其中 x 即最初傳入的 250 。現在可以這樣來獲得各種參數中test1的運行時間。

第二步,實現裝飾器

剛剛說到兩個點,已經解決了一個,再去解決調用方法。在test1函數加一個@test2,這是一個語法糖, 效果如下:

def test2(func):
	def test3(x):
		start_time = time.time()	# 開始時間
		func(x)						# 要測試的函數	
		stop_time = time.time()		# 結束時間
		delta_time = stop_time - start_time    # 時間間隔,即運行時間
		return '{:f}'.format(delta_time)  	   # f返回格式化時間
	return test3

@test2
def test1(a):
	return a

運行一下

>>> test1(250)
'0.000001'

加入語法糖之後直接調用test1就得出了想要的結果。那麼這個語法糖的作用就顯而易見了。 就是替代test2(test1)的。

就是說test2(test1)(250) 直接被語法糖省略成了test1(250),這二者是相同的效果,但是滿足了不改變調用方法,而且我們自始至終都沒有修改過test1裏面的源代碼。

這就是裝飾器。(2018.11.16)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章