不少人對裝飾器難以理解,緣由是因爲如下三點內容沒有搞清楚:html
- 關於函數「變量」(或「變量」函數)的理解
- 關於高階函數的理解
- 關於嵌套函數的理解
一、裝飾器
裝飾器實際上就是爲了給某程序增添功能,但該程序已經上線或已經被使用,那麼就不能大批量的修改源代碼,這樣是不科學的也是不現實的,由於就產生了裝飾器,使得其知足:python
- 不能修改被裝飾的函數的源代碼
- 不能修改被裝飾的函數的調用方式
- 知足一、2的狀況下給程序增添功能
那麼根據需求,同時知足了這三點原則,這纔是咱們的目的。由於,下面咱們從解決這三點原則入手來理解裝飾器。數據庫
等等,我要在需求以前先說裝飾器的原則組成:閉包
< 函數+實參高階函數+返回值高階函數+嵌套函數+語法糖 = 裝飾器 >
這個式子是貫穿裝飾器的靈魂所在!app
裝飾器:本質就是函數,功能是爲其餘函數添加功能; #原則 #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的返回值
給功能函數加上參數:post
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的返回值
給函數加上認證功能:url
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("老王")
二、需求的實現
假設有代碼:spa
1
2
3
4
5
|
improt time
def
test():
time.sleep(
2
)
print
(
"test is running!"
)
test()
|
很顯然,這段代碼運行的結果必定是:等待約2秒後,輸出code
1
|
test
is
running
|
- 那麼要求在知足三原則的基礎上,給程序添加統計運行時間(2 second)功能
在行動以前,咱們先來看一下文章開頭提到的緣由1(關於函數「變量」(或「變量」函數)的理解)
2.一、函數「變量」(或「變量」函數)
假設有代碼:
1
2
3
4
5
|
x
=
1
y
=
x
def
test1():
print
(
"Do something"
)
test2
=
lambda
x:x
*
2
|
那麼在內存中,應該是這樣的:
很顯然,函數和變量是同樣的,都是「一個名字對應內存地址中的一些內容」
那麼根據這樣的原則,咱們就能夠理解兩個事情:
- test1表示的是函數的內存地址
- test1()就是調用對在test1這個地址的內容,即函數
若是這兩個問題能夠理解,那麼咱們就能夠進入到下一個緣由(關於高階函數的理解)
2.2高階函數
那麼對於高階函數的形式能夠有兩種:
- 把一個函數名看成實參傳給另一個函數(「實參高階函數」)
- 返回值中包含函數名(「返回值高階函數」)
那麼這裏面所說的函數名,實際上就是函數的地址,也能夠認爲是函數的一個標籤而已,並非調用,是個名詞。若是能夠把函數名當作實參,那麼也就是說能夠把函數傳遞到另外一個函數,而後在另外一個函數裏面作一些操做,根據這些分析來看,這豈不是知足了裝飾器三原則中的第一條,即不修改源代碼而增長功能。那咱們看來一下具體的作法:
仍是針對上面那段代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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處,把函數名後面加上括號,就是對函數的調用(執行它)。所以,這段代碼運行結果是:
1
2
|
test
is
running!
the run time
is
3.0009405612945557
|
咱們看到彷佛是達到了需求,即執行了源程序,同時也附加了計時功能,可是這隻知足了原則1(不能修改被裝飾的函數的源代碼),但這修改了調用方式。假設不修改調用方式,那麼在這樣的程序中,被裝飾函數就沒法傳遞到另外一個裝飾函數中去。
那麼再思考,若是不修改調用方式,就是必定要有test()這條語句,那麼就用到了第二種高階函數,即返回值中包含函數名
以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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(),會重複執行。這裏只是爲了說明第二種高階函數的思想,下面才真的進入重頭戲。
2.3 嵌套函數
嵌套函數指的是在函數內部定義一個函數,而不是調用,如:
1
2
3
4
5
6
|
def
func1():
def
func2():
pass
而不是
def
func1():
func2()
|
另外還有一個題外話,函數只能調用和它同級別以及上級的變量或函數。也就是說:裏面的能調用和它縮進同樣的和他外部的,而內部的是沒法調用的。
那麼咱們再回到咱們以前的那個需求,想要統計程序運行時間,而且知足三原則。
代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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())。
至此,一切皆合,大功告成,單隻差一步。
三、 真正的裝飾器
根據以上分析,裝飾器在裝飾時,須要在每一個函數前面加上:
1
|
test
=
timer(test)
|
顯然有些麻煩,Python提供了一種語法糖,即:
1
|
@timer
|
這兩句是等價的,只要在函數前加上這句,就能夠實現裝飾做用。
以上爲無參形式
四、裝飾有參函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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。
完整代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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()的結果有返回值呢?好比:
1
2
3
4
|
def
test(parameter):
time.sleep(
2
)
print
(
"test is running!"
)
return
"Returned value"
|
那麼面對這樣的函數,若是用上面的代碼來裝飾,最後一行的test()實際上調用的是deco()。有人可能會問,func()不就是test()麼,怎麼沒返回值呢?
實際上是有返回值的,可是返回值返回到deco()的內部,而不是test()即deco()的返回值,那麼就須要再返回func()的值,所以就是:
1
2
3
4
5
6
7
8
9
|
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處返回。
完整程序爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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()
|
五、帶參數的裝飾器
又增長了一個需求,一個裝飾器,對不一樣的函數有不一樣的裝飾。那麼就須要知道對哪一個函數採起哪一種裝飾。所以,就須要裝飾器帶一個參數來標記一下。例如:
1
|
@decorator
(parameter
=
value)
|
好比有兩個函數:
1
2
3
4
5
6
7
8
9
10
|
def
task1():
time.sleep(
2
)
print
(
"in the task1"
)
def
task2():
time.sleep(
2
)
print
(
"in the task2"
)
task1()
task2()
|
要對這兩個函數分別統計運行時間,可是要求統計以後輸出:
1
|
the task1
/
task2 run time
is
:
2.00
……
|
因而就要構造一個裝飾器timer,而且須要告訴裝飾器哪一個是task1,哪一個是task2,也就是要這樣:
1
2
3
4
5
6
7
8
9
10
11
12
|
@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參數傳遞到裝飾器中,咱們以往的裝飾器,都是傳遞函數名字進去,而此次,多了一個參數,要怎麼作呢?
因而,就想到再加一層函數來接受參數,根據嵌套函數的概念,要想執行內函數,就要先執行外函數,才能調用到內函數,那麼就有:
1
2
3
4
5
6
7
8
9
10
11
|
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)也恰巧帶了括號,那麼就會執行這個函數, 那麼就是至關於:
1
2
|
timer
=
timer(parameter)
task1
=
timer(task1)
|
後面的運行就和通常的裝飾器同樣了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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()
|
至此,裝飾器的所有內容結束。