函數【八】裝飾器

python裝飾器

 

不少人對裝飾器難以理解,緣由是因爲如下三點內容沒有搞清楚:html

  1. 關於函數「變量」(或「變量」函數)的理解
  2. 關於高階函數的理解
  3. 關於嵌套函數的理解

一、裝飾器

裝飾器實際上就是爲了給某程序增添功能,但該程序已經上線或已經被使用,那麼就不能大批量的修改源代碼,這樣是不科學的也是不現實的,由於就產生了裝飾器,使得其知足:python

  1. 不能修改被裝飾的函數的源代碼
  2. 不能修改被裝飾的函數的調用方式
  3. 知足一、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
=  1
=  x
def  test1():
     print ( "Do something" )
test2  =  lambda  x:x * 2

 那麼在內存中,應該是這樣的:

很顯然,函數和變量是同樣的,都是「一個名字對應內存地址中的一些內容」 
那麼根據這樣的原則,咱們就能夠理解兩個事情:

  1. test1表示的是函數的內存地址
  2. test1()就是調用對在test1這個地址的內容,即函數

若是這兩個問題能夠理解,那麼咱們就能夠進入到下一個緣由(關於高階函數的理解)

2.2高階函數

那麼對於高階函數的形式能夠有兩種:

  1. 把一個函數名看成實參傳給另一個函數(「實參高階函數」)
  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
=  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()

 至此,裝飾器的所有內容結束。

相關文章
相關標籤/搜索