Python:5分鐘理解裝飾器的本質

 

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也很好解釋,由於我們也不確定我們給加裝飾器的函數參數是什麼樣的,使用這兩個參數可以面向不同函數的參數而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章