1.裝飾器的應用場景
當我們想對某函數賦予額外功能,特別是當許多函數都需要這個功能時(如對某函數的執行時間計時),首先我們不可能對函數一個一個的加代碼,那樣太耗時,其次我們又不想破壞原來函數本身的代碼整潔度(比如計時就不算原來函數功能的一部分)。這時候,裝飾器就起到了作用。
2.爲什麼不直接用函數嵌套
說到這裏,我們可能會想到,那就直接用函數嵌套唄,比如下面這段代碼
def pr():
print('aaa')
pr()
使用函數嵌套之後的結果
def pr():
print('aaa')
def count_time(func):
time1 = time.time()
func()
time2 = time.time()
print(time2-time1)
count_time(pr) # 可是函數調用的部分變了
雖然我們沒有動原來的函數,但是使用函數嵌套之後,我們需要更改所有調用函數,比如這裏需要把pr()改爲count_time(pr)
3.裝飾器的@
@count_time
def pr():
print('aaa')
裝飾器常常要在需要加的函數前面寫個@,這個@的意思其實就是表示把函數名作爲一個參數傳到另一個函數中,比如上面這串代碼中@count_time與pr=count_time(pr)是一個意思。
也就是說,我們寫pr就等價於運行函數count_time(pr)
那我們可以這麼改
def count_time(func):
time1 = time.time()
func()
time2 = time.time()
print(time2-time1)
@count_time
def pr():
print('aaa')
pr # 這裏調用的部分還是不同
我們發現調用那裏還是需要修改,那怎麼辦?所以,我們需要在count_time中再嵌套一層函數
def count_time(func):
def f():
time1 = time.time()
func()
time2 = time.time()
print(time2-time1)
return f
@count_time
def pr():
print('aaa')
pr() # 這裏就一樣了,沒有改變調用方式
爲什麼這裏就一樣了?實際上,這裏是count_time的返回值是f,我們實際上調用的是f,之前說過@count_time相當於pr=count_time(pr),後面調用語句pr()=count_time(pr)()=f()
注:這裏的f我們習慣寫爲wrapper
所以我們發現,我們原來執行的函數加上@function後,變成了執行function下的子函數wrapper了
4.當有參數傳入時呢?
我們知道當加上裝飾器時,我們其實執行的已經不是函數本身了,所以,當我們有需要傳入的參數時,這個參數一定要傳遞到wrapper那裏,並且有需要返回的值,也必須在wrapper內利用一個值存儲下來並返回。
所以我們這麼改:
def count_time(func):
def wrapper(*args,**kwargs):
time1 = time.time()
result = func(*args,**kwargs) # 存儲pr函數執行結果
time2 = time.time()
print(time2-time1)
return result # 代替pr函數返回
return wrapper
@count_time
def pr(a,b):
c = a+b
return c
print(pr(1,2))
這樣的話,實際上pr(1,2) = count_time(pr)(1,2)=wrapper(1,2)
至於爲啥wrapper後面使用*args,**kwargs也很好解釋,由於我們也不確定我們給加裝飾器的函數參數是什麼樣的,使用這兩個參數可以面向不同函數的參數而已。