有借用,但原文出處已經找不到了,根據筆記分享一下解釋器的基礎。
下面的代碼表示,等待兩秒鐘,輸出‘test is running'。
現在要求增加統計程序運行時間的功能。
等待兩秒鐘,輸出‘test is running',現要求增加統計程序運行時間的功能。
import time
def test():
time.sleep(2)
print('test is running!')
test()
學習先問為什么,有什么用,學基礎的時候有時不理解為什么簡單的事情要搞那么復雜,而對應的實際應用時,往往“這個復雜方法有很多優點和簡化了很多事情”。學基礎時用簡單的例子,只是為了講明知識點,而不要去關注例子實現的什么功能,這個功能當然是個‘屁’了。
那么,裝飾器的實際應用場景:程序已上線或已使用,但需要增加新功能,修改原函數不合理也不科學(這里真的包含了很多背景聲音),就要求:
(1)不能修改原功能函數的代碼。例子中的功能是非常簡單的,要實現當然是直接修改,但現實中一個功能的實現是非常復雜的代碼,直接修改是不合理不科學的。
(2)不能修改原函數的調用方式。在符合第一條的情況下,調用時仍然是用test(),即老方法能實現新功能。現實中后面程序在反復用test(),就是要完全不動老代碼的基礎上,增加一個’裝飾器‘更新整個程序。
(3)上述說的老方法是表面上的,這就是裝飾器介入的作用。
好,以下開始改造,用試錯的方法解釋裝飾器的實現過程和原理。
滿足條件(1)可以增加代碼如下:
def deco(func):
start=time.time()
func()
stop=time.time()
print(stop-start)
deco(test)
#把老代碼里的test函數作為參數傳遞給新函數
#(因為是增加新功能,老功能還是要的,所以新代碼里肯定要引用老代碼的功能,就采取了這種方法)
看上去新功能就實現了,但可以看到調用方式變為了deco(test),即違背了條件(2),以前程序里可能反復調用了test(),都要改成deco(test),不科學。
同時滿足條件(2),再改:
思路:產生一個新函數,重新賦值給test,test=某函數,那么后面反復調用的test()就被實質上改過來了,實現功能。
#試一下:
test=deco(test) #計算右邊的,是一個兩行字符串,賦值給test根本不是一個函數。
test() #因此跳錯誤碼
再試一下:
增加:return func
然后:
test=deco(test)
test()
代碼正常運行,但因為return func的縮進和其他代碼是同級的,所以deco(test)這步已經把新功能給實現了,又返回了一次test(),結果重復。
把下面完整的代碼去試一下,出現了兩次test is running ,不合格。
import time
def test():
time.sleep(2)
print('test is running!')
def deco(func):
start=time.time()
func()
stop=time.time()
print(stop-start)
return func
test=deco(test)
test()
思路沒有錯,上述這里return一個函數的思考過程也是對的,只是返回的方式不對,應當使用’嵌套函數‘。
def timer(func):
def deco():
start=time.time()
func()
stop=time.time()
print(stop-start)
return deco
#把deco()嵌套在timer(func)里,返回一個deco()函數的函數名deco;
#調用timer(func)時,deco()里面的過程代碼類似于沒有執行的,
#被打包成了一個函數,執行的只是return了這個函數的函數名。
test=timer(test)
#右邊代碼返回的是一個deco()的函數名deco,deco()打包了作為被參數傳入的原test()里的功能,
#同時增加了新功能。
test()
#符合(2)要求,沒有改變調用方法,但實質上這句調用的已經是deco()了。
#頂格代碼是不能調用經過了二次縮進的嵌套函數的,
#但這里用了deco()用了return,成了閉包函數,就可以被調用了。
至此,本質上是修改了調用函數,但在表面上并未修改調用方式,而且實現了附加功能,需求實現。
真正的裝飾器:語法糖
在原test()的上一行加上@timer,刪除test=timer(test).
而且要把新增的函數寫在原函數的上面
最后代碼
import time
def timer(func):
print(func.__name__) #看看打印出來的是什么
def deco():
start=time.time()
func()
stop=time.time()
print(stop-start)
return deco
@timer
def test():
time.sleep(2)
print('test is running')
#test=timer(test)這句不用了,注釋掉
test()
過程比較啰嗦,但每一步都看下來后,裝飾 器的原理就知道了,涉及到有參數的時候,再另外理解了。