不少人對裝飾器難以理解,緣由是因爲如下三點內容沒有搞清楚:python
裝飾器實際上就是爲了給某程序增添功能,但該程序已經上線或已經被使用,那麼就不能大批量的修改源代碼,這樣是不科學的也是不現實的,由於就產生了裝飾器,使得其知足:數據庫
那麼根據需求,同時知足了這三點原則,這纔是咱們的目的。由於,下面咱們從解決這三點原則入手來理解裝飾器。閉包
等等,我要在需求以前先說裝飾器的原則組成:app
這個式子是貫穿裝飾器的靈魂所在!ide
裝飾器:本質就是函數,功能是爲其餘函數添加功能; #原則 #1:不能修改被修飾函數的源代碼; #2:不能修改被修飾函數的調用方式 #裝飾器的知識儲備 #裝飾器 = 高階函數+函數嵌套+閉包 import time def demo(func): def data(): star_time = time.time() func() end_time = time.time() print("執行時間%s"%(end_time - star_time))return data @demo #至關於inner=demo(inner) def inner(): time.sleep(1) print("執行結果") inner() # inner() #執行的是data # inner=demo(inner) #返回的是data的地址 # print(inner)# ,<function demo.<locals>.data at 0x00000128A495E950>
給功能函數加上返回值:函數
import time def demo(func): def data(): star_time = time.time() res = func() #就是在運行inner,用res接收inner的返回值 end_time = time.time() print("執行時間%s"%(end_time - star_time)) return res return data @demo def inner(): time.sleep(1) print("執行結果") return "這是inner的返回值" print(inner()) #執行inner並打印inner的返回值
給功能函數加上參數:spa
import time def demo(func): def data(*args,**kwargs): star_time = time.time() res = func(*args,**kwargs) #就是在運行inner,用res接收inner的返回值 end_time = time.time() print("執行時間%s"%(end_time - star_time)) return res return data @demo def inner(name,age): time.sleep(1) print("執行結果,名字是【%s】年齡是【%s】"%(name,age)) return "這是inner的返回值" print(inner("alex",18)) #執行inner並打印inner的返回值
給函數加上認證功能:code
user_list = [ {"name":"alex","passwd":"123"}, {"name":"lw","passwd":"456"}, {"name":"szx","passwd":"000"}, ]#帳戶數據庫 current_dic = {"username":None,"login":False} #當前登錄狀態 def auth_func(func): def wrapper(*args,**kwargs): if current_dic["username"] and current_dic["login"]:#判斷當前登陸狀態爲真時,執行函數; res = func(*args, **kwargs) return res username = input("用戶名:") passwd = input("密碼:") for user_dic in user_list: #遍歷帳戶列表 if username == user_dic["name"] and passwd == user_dic["passwd"]: current_dic["username"] = username #更改登陸狀態 current_dic["login"] = True res = func(*args,**kwargs) return res else: print("用戶名或者密碼錯誤") return wrapper @auth_func def index(): print("歡迎來到京東主頁") @auth_func def home(name): print("歡迎回家%s" %name) @auth_func def shopping_car(name): print("%s購物車裏有娃娃、奶茶"%(name)) index() home("老王") shopping_car("老王")
假設有代碼:blog
improt time def test(): time.sleep(2) print("test is running!") test()
很顯然,這段代碼運行的結果必定是:等待約2秒後,輸出內存
test is running
在行動以前,咱們先來看一下文章開頭提到的緣由1(關於函數「變量」(或「變量」函數)的理解)
假設有代碼:
x = 1 y = x def test1(): print("Do something") test2 = lambda x:x*2
那麼在內存中,應該是這樣的:
很顯然,函數和變量是同樣的,都是「一個名字對應內存地址中的一些內容」
那麼根據這樣的原則,咱們就能夠理解兩個事情:
若是這兩個問題能夠理解,那麼咱們就能夠進入到下一個緣由(關於高階函數的理解)
那麼對於高階函數的形式能夠有兩種:
那麼這裏面所說的函數名,實際上就是函數的地址,也能夠認爲是函數的一個標籤而已,並非調用,是個名詞。若是能夠把函數名當作實參,那麼也就是說能夠把函數傳遞到另外一個函數,而後在另外一個函數裏面作一些操做,根據這些分析來看,這豈不是知足了裝飾器三原則中的第一條,即不修改源代碼而增長功能。那咱們看來一下具體的作法:
仍是針對上面那段代碼:
improt time def test(): time.sleep(2) print("test is running!") def deco(func): start = time.time() func() #2 stop = time.time() print(stop-start) deco(test) #1
咱們來看一下這段代碼,在#1處,咱們把test看成實參傳遞給形參func,即func=test。注意,這裏傳遞的是地址,也就是此時func也指向了以前test所定義的那個函數體,能夠說在deco()內部,func就是test。在#2處,把函數名後面加上括號,就是對函數的調用(執行它)。所以,這段代碼運行結果是:
test is running! the run time is 3.0009405612945557
咱們看到彷佛是達到了需求,即執行了源程序,同時也附加了計時功能,可是這隻知足了原則1(不能修改被裝飾的函數的源代碼),但這修改了調用方式。假設不修改調用方式,那麼在這樣的程序中,被裝飾函數就沒法傳遞到另外一個裝飾函數中去。
那麼再思考,若是不修改調用方式,就是必定要有test()這條語句,那麼就用到了第二種高階函數,即返回值中包含函數名
以下代碼:
improt time def test(): time.sleep(2) print("test is running!") def deco(func): print(func) return func t = deco(test) #3 #t()#4 test()
咱們看這段代碼,在#3處,將test傳入deco(),在deco()裏面操做以後,最後返回了func,並賦值給t。所以這裏test => func => t,都是同樣的函數體。最後在#4處保留了原來的函數調用方式。
看到這裏顯然會有些困惑,咱們的需求不是要計算函數的運行時間麼,怎麼改爲輸出函數地址了。是由於,單獨採用第二張高階函數(返回值中包含函數名)的方式,而且保留原函數調用方式,是沒法計時的。若是在deco()裏計時,顯然會執行一次,而外面已經調用了test(),會重複執行。這裏只是爲了說明第二種高階函數的思想,下面才真的進入重頭戲。
嵌套函數指的是在函數內部定義一個函數,而不是調用,如:
def func1(): def func2(): pass 而不是 def func1(): func2()
另外還有一個題外話,函數只能調用和它同級別以及上級的變量或函數。也就是說:裏面的能調用和它縮進同樣的和他外部的,而內部的是沒法調用的。
那麼咱們再回到咱們以前的那個需求,想要統計程序運行時間,而且知足三原則。
代碼:
improt time def timer(func) #5 def deco(): start = time.time() func() stop = time.time() print(stop-start) return deco test = timer(test) #6 def test(): time.sleep(2) print("test is running!") test() #7
這段代碼可能會有些困惑,怎麼突然多了這麼多,暫且先接受它,分析一下再來講爲何是這樣。
首先,在#6處,把test做爲參數傳遞給了timer(),此時,在timer()內部,func = test,接下來,定義了一個deco()函數,當並未調用,只是在內存中保存了,而且標籤爲deco。在timer()函數的最後返回deco()的地址deco。
而後再把deco賦值給了test,那麼此時test已經不是原來的test了,也就是test原來的那些函數體的標籤換掉了,換成了deco。那麼在#7處調用的其實是deco()。
那麼這段代碼在本質上是修改了調用函數,但在表面上並未修改調用方式,並且實現了附加功能。
把函數當作是盒子,test是小盒子,deco是中盒子,timer是大盒子。程序中,把小盒子test傳遞到大盒子temer中的中盒子deco,而後再把中盒子deco拿出來,打開看看(調用)
這樣作的緣由是:
咱們要保留test(),還要統計時間,而test()只能調用一次(調用兩次運行結果會改變,不知足),再根據函數即「變量」,那麼就能夠經過函數的方式來回閉包。因而乎,就想到了,把test傳遞到某個函數,而這個函數內恰巧內嵌了一個內函數,再根據內嵌函數的做用域(能夠訪問同級及以上,內嵌函數能夠訪問外部參數),把test包在這個內函數當中,一塊兒返回,最後調用這個返回的函數。而test傳遞進入以後,再被包裹出來,顯然test函數沒有弄丟(在包裹裏),那麼外面剩下的這個test標籤正好能夠替代這個包裹(內含test())。
至此,一切皆合,大功告成,單隻差一步。
根據以上分析,裝飾器在裝飾時,須要在每一個函數前面加上:
test = timer(test)
顯然有些麻煩,Python提供了一種語法糖,即:
@timer
這兩句是等價的,只要在函數前加上這句,就能夠實現裝飾做用。
以上爲無參形式
improt time def timer(func) def deco(): start = time.time() func() stop = time.time() print(stop-start) return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") test()
對於一個實際問題,每每是有參數的,若是要在#8處,給被修飾函數加上參數,顯然這段程序會報錯的。錯誤緣由是test()在調用的時候缺乏了一個位置參數的。而咱們知道test = func = deco,所以test()=func()=deco()
,那麼當test(parameter)有參數時,就必須給func()和deco()也加上參數,爲了使程序更加有擴展性,所以在裝飾器中的deco()和func(),加如了可變參數*agrs和 **kwargs。
完整代碼以下:
improt time def timer(func) def deco(*args, **kwargs): start = time.time() func(*args, **kwargs) stop = time.time() print(stop-start) return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") test()
那麼咱們再考慮個問題,若是原函數test()的結果有返回值呢?好比:
def test(parameter): time.sleep(2) print("test is running!") return "Returned value"
那麼面對這樣的函數,若是用上面的代碼來裝飾,最後一行的test()實際上調用的是deco()。有人可能會問,func()不就是test()麼,怎麼沒返回值呢?
實際上是有返回值的,可是返回值返回到deco()的內部,而不是test()即deco()的返回值,那麼就須要再返回func()的值,所以就是:
def timer(func) def deco(*args, **kwargs): start = time.time() res = func(*args, **kwargs)#9 stop = time.time() print(stop-start) return res#10 return deco
其中,#9的值在#10處返回。
完整程序爲:
improt time def timer(func) def deco(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print(stop-start) return res return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") return "Returned value" test()
又增長了一個需求,一個裝飾器,對不一樣的函數有不一樣的裝飾。那麼就須要知道對哪一個函數採起哪一種裝飾。所以,就須要裝飾器帶一個參數來標記一下。例如:
@decorator(parameter = value)
好比有兩個函數:
def task1(): time.sleep(2) print("in the task1") def task2(): time.sleep(2) print("in the task2") task1() task2()
要對這兩個函數分別統計運行時間,可是要求統計以後輸出:
the task1/task2 run time is : 2.00……
因而就要構造一個裝飾器timer,而且須要告訴裝飾器哪一個是task1,哪一個是task2,也就是要這樣:
@timer(parameter='task1') # def task1(): time.sleep(2) print("in the task1") @timer(parameter='task2') # def task2(): time.sleep(2) print("in the task2") task1() task2()
那麼方法有了,可是咱們須要考慮如何把這個parameter參數傳遞到裝飾器中,咱們以往的裝飾器,都是傳遞函數名字進去,而此次,多了一個參數,要怎麼作呢?
因而,就想到再加一層函數來接受參數,根據嵌套函數的概念,要想執行內函數,就要先執行外函數,才能調用到內函數,那麼就有:
def timer(parameter): # print("in the auth :", parameter) def outer_deco(func): # print("in the outer_wrapper:", parameter) def deco(*args, **kwargs): return deco return outer_deco
首先timer(parameter),接收參數parameter=’task1/2’,而@timer(parameter)也恰巧帶了括號,那麼就會執行這個函數, 那麼就是至關於:
timer = timer(parameter) task1 = timer(task1)
後面的運行就和通常的裝飾器同樣了:
import time def timer(parameter): def outer_wrapper(func): def wrapper(*args, **kwargs): if parameter == 'task1': start = time.time() func(*args, **kwargs) stop = time.time() print("the task1 run time is :", stop - start) elif parameter == 'task2': start = time.time() func(*args, **kwargs) stop = time.time() print("the task2 run time is :", stop - start) return wrapper return outer_wrapper @timer(parameter='task1') def task1(): time.sleep(2) print("in the task1") @timer(parameter='task2') def task2(): time.sleep(2) print("in the task2") task1() task2()
至此,裝飾器的所有內容結束。