** 裝飾器**
1.裝飾器的概念
- 裝飾器的本質就是一個函數,它的作用是為其他函數添加一個新的功能,但是不改變原函數的源代碼和調用方式。
-
裝飾器的兩大原則:
- 不修改被修飾函數的源代碼
- 不修改被修飾函數的調用方式
2.裝飾器的知識儲備(或者我們可以理解成,一個裝飾器是由什么組成)
- 裝飾器 = 高階函數+函數嵌套+閉包
3.裝飾器的實現
- 首先我們定義一個累加求和的函數
import time
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
cal()
- 這是我們的一個初始函數,如果此時我們需要為這個函數添加一個功能,例如我們需要計算這個函數需要的時間。想想好像也容易做到這一點的吧。通過調用函數前和函數運行結束后分別記下時間戳,那么我們就成功的為這個函數記下了運行的時間。
import time
def cal():
start_time = time.time()
res = 0
for i in range(100):
res += i
time.sleep(1)
stop_time = time.time()
print("函數的運行結果為", res)
print("函數的運行時間為",stop_time-start_time)
return res
cal()
- 通過上面的這種方法,我們很容易就達到了我們需要做得目的了,也就是說我們成功的為函數添加了一個新的功能——記錄函數運行的時間。不過,這只是其中的一個函數,如果說有成百上千個函數,我們也要這樣一個個的添加功能嗎?這顯然不太現實。
- 更關鍵的一點,我們這種做法已經違反了開放封閉原則。開放封閉原則簡單的理解就是我們寫的程序上線以后,我們就不能對程序的源代碼經行修改。如果函數在其他位置被調用了的話,我們便不知道會不會出現其他的后果,有可能引起一系列的連鎖反應。所以說,上面的方法,我們是行不通的。
- 裝飾器的第二個原則: 不修改被修飾函數的調用方式 ,我可以為函數添加新的功能,但是不能改變它的調用方式,在上面的函數中,我們通過cal()進行調用函數,那么我們添加了功能之后,我們就必須按照原來的方法,用cal()來調用函數。
-
高階函數的使用
-
首先我們說一下,什么是高階函數?
滿足下面兩個條件中的其中一個都可以成為高階函數:- 函數接受的參數是一個函數名
- 函數的返回值是一個變量名
- 我們繼續拿上面的函數做例子
-
首先我們說一下,什么是高階函數?
import time
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
def test(func):
print(func)
func()
test(cal)
那么這里的test就是一個高階函數。因為它接收的參數是一個函數名func,看到這里,我們就可以進行對函數cal添加新的功能了,繼續以記錄函數運行時間作為一個例子。
import time
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
def test(func):
print(func)
start_time = time.time()
func()
stop_time = time.time()
print("函數運行的時間為",stop_time-start_time )
test(cal)
來看下這個函數運行的結果:
那這時候,我們不但沒有修改原函數的源代碼,而且還為函數添加了一個新的功能,我們的功能就實現了,裝飾器就這么結束了。難道真的結束了嗎?你注意到了沒,函數的方式已經發生改變了,初始我們調用函數使用cal(),而現在卻是test(cal),這就違反了我們上述所說的裝飾器的第二原則。
那么我們來看下高階函數第二種定義:返回值是一個函數名
import time
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
def test(func):
start_time = time.time()
cal()
stop_time = time.time()
print("函數的運行時間為",stop_time-start_time)
return func
cal = test(cal)
# print(res)
cal()
通過傳入參數,能夠讓我們避免對源代碼的修改,但修改了函數的調用方式
通過返回值為函數名,能夠讓我們避免修改函數的調用方式
這時我們通過對添加功能函數的參數傳入為函數,返回值為函數名,此時,我們滿足了裝飾器的兩個原則。我們這種貌似是可取的,其實不然。我們可以發現在添加功能時,需要對函數執行一次才可以,這不符合我們的要求。
因此,僅僅只有高階函數并不能滿足于對我們的需求,不知道還是否記得開頭寫的對裝飾器的知識儲備。裝飾器=高階函數+函數嵌套+閉包。接下來,我們來了解什么時閉包吧。
4.閉包
- 閉包的理解 :有很多小伙伴都不懂什么是什么,比較模糊,其實我們可以理解閉包為一種特殊的嵌套函數,這個函數由兩個函數嵌套組成,那么在結構上就有外函數和內函數的說法。 外函數返回值是內函數的引用結果
-
(1)外層函數out_func可以調用內層函數in_func,但無法引用in_func內部的變量y
-
(2)內層函數in_func可以引用外層函數out_func的變量x
def out_func():
x = 1
def in_func(y):
return x+y
return in_func
test = out_func()
print(test(10))
- test是閉包函數,自由變量是x,因為它不是in_func這個函數內部的變量,但是卻被in_func引用。test的結果是函數in_func的地址,那么通過test()便可調用函數in_test.
- 每一層的函數,我們可以成為一個包,而閉就是封裝的意思,它封裝的是變量,嵌套在函數內的函數等價于一個變量,遵循函數即變量的原則。其實閉包有點類似于作用域,若最里層需要一個變量,我們就可以在當前層定義,如果當前層不允許,則往上一層,一層一層的往外,也就是說我們可以在最外層定義一個變量,那就可以滲透到最里層
- 閉包的最大用處也是用于裝飾器,接下來,讓我們看看裝飾器的作用吧。
5.裝飾器的實現
-
我們繼續以上面的例子來了解裝飾器,我們要為一個函數計算運行時間。
來,先看一段代碼吧。
import time
def timer(func):
def wrapper():
# print(func)
start_time = time.time()
func()
stop_time = time.time()
print(stop_time-start_time)
return wrapper
#@timer #等價于 cal = timer(cal)
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
cal = timer(cal)
cal()
事到如此,我們基本 完成了裝飾器的功能了,我們不但沒有修改函數的源代碼,也沒有修改其調用方式,還為它添加了一個新的功能。
不過,這樣子還存在這一丟丟瑕疵,因為每次給函數添加功能是都需要做一次賦值操作,那能不能不每次調用都賦值一次? 那就要用到了一個 @,這是python提供的一個功能。
@timer 相當于 cal = timer(cal),我們要修飾那個函數,就在那個函數前添加@timer
6.含返回值的裝飾器
- 上面的樣子看上去似乎已經滿足了我們的要求了,但好像還有點小問題額。。。如果我們需要用到cal函數的返回結果的時候,這貌似不符合我們的心意啊。
res = cal()
print("這是函數的返回結果的cal:",res)
- 仔細研究一下,cal經過裝飾后,本質上就是上面的經過升級后的wrapper函數,而這個函數是沒有返回值的,因此,cal()運行的結果返回就是默認的None啦。
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print(stop_time-start_time)
- 來看看這一段wrapper函數,我們需要func的返回值,那么就需要在wrapper函數加上return返回咯,返回的值是func的結果,那就簡單了,吧func返回的結果賦值給變量res,然后return返回res這樣就解決啦。
import time
def timer(func):
def wrapper():
# print(func)
start_time = time.time()
res = func()
stop_time = time.time()
print(stop_time-start_time)
return res
return wrapper
@timer
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
res = cal()
print("這是函數的返回結果的cal:",res)
7.含參數的裝飾器
上面的例子,我們的cal函數都是不帶參數的,但是,我們并不能保證以后我們所寫的每一個函數都是不帶參數的。
- 舉個例子吧,我們在原來的基礎上定義了一個test1函數:
def timer(func):
def wrapper():
# print(func)
start_time = time.time()
res = func()
stop_time = time.time()
return res
return wrapper
@timer
def cal():
res = 0
for i in range(100):
res+=i
time.sleep(1)
print("函數的運行結果為",res)
return res
@timer
def test1(name):
time.sleep(1)
print("這是%s"%(name))
return res
我們在不修改裝飾器的情況下,就會報錯了,wrapped函數不需要函數,但是一個給出了。
那么我們就要在wrapped函數中加上參數name,不過,依然不能解決問題。
這是我們就需要用到了可變參數了。 如果我們不確定要往函數中傳入多少個參數,或者我們想往函數中以列表和元組的形式傳參數時,那就使要用*args; 如果我們不知道要往函數中傳入多少個關鍵詞參數,或者想傳入字典的值作為關鍵詞參數時,那就要使用**kwargs。
def timer(func):
def wrapper(*args,**kwargs):
# print(func)
start_time = time.time()
res = func(*args,**kwargs)
stop_time = time.time()
return res
return wrapper
這里我們對wrapper函數進行修改,在wrapper中就如位置可變參數和關鍵字可變參數,這樣的話,我們就不用再擔心被修飾函數有多少個參數,以及我們定義的裝飾器可以修飾任何函數啦。
好啦,裝飾器就到此結束了。。。。。。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
