重點面試知識——多個裝飾器疊加的原理

當一個被裝飾的對象同時疊加多個裝飾器時

裝飾器的加載順序是:由下而上

裝飾器的執行順序是:由上而下

加載裝飾器就是將原函數名與裝飾器內部的wrapper函數進行偷樑換柱

執行裝飾器實際上就是執行裝飾器內部的wrapper函數。

我們來看下面這段代碼

我們定義了兩個裝飾器:無參裝飾器timmer與有參裝飾器auth

我們用這兩個裝飾器去修飾index函數。讓我們看看到底是發生了一件什麼事

import time


def timmer(func):      
    def wrapper1(*args, **kwargs):
        print('===================================>wrapper1運行了')
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res

    return wrapper1


def auth(engine='file'):
    def xxx(func):
        def wrapper2(*args, **kwargs):
            print('===================================>wrapper2運行了')
            name = input('username>>>: ').strip()
            pwd = input('password>>>: ').strip()
            if engine == 'file':
                print('基於文件的認證')
                if name == 'egon' and pwd == '123':
                    print('login successfull')
                    res = func(*args, **kwargs)
                    return res
            elif engine == 'mysql':
                print('基於mysql的認證')
            elif engine == 'ldap':
                print('基於ldap的認證')
            else:
                print('錯誤的認證源')

        return wrapper2

    return xxx


@timmer
@auth(engine='file')
def index():
    print('welcome to index page')
    time.sleep(2)


index()

文章一開頭,我們就已經說了,裝飾器的加載順序是由下而上的。就是說離被裝飾函數最近的裝飾器先加載。

那麼在這段代碼中可以看到,是@auth(engine='file')先被加載,在加載的時候,python一遇到函數名+()就會觸發函數的執行,也就是說,加載的第一步就是執行了auth(engine='file')函數。

我們拐過頭來去看auth函數的代碼,執行auth函數其實就幹了兩件事,定義一個叫xxx的函數,然後將xxx函數內存地址作爲返回值返回。沒有調用xxx就不會觸發xxx函數的執行。

auth(engine='file')函數執行完畢以後,語法糖:'@'符號開始生效,它的作用就是將正下方的函數名(函數內存地址)作爲參數傳入裝飾器並將原函數名作爲變量名去接收。

在這裏就是幹了這樣一件事:index=xxx(index)(這裏需要注意。第一:爲什麼是xxx呢?因爲在auth函數執行完畢後把xxx函數的內存地址作爲返回值返回了。所以這裏是xxx。第二:括號內的index和等號左邊的index不是同一個東西,括號內的index是原函數index的內存地址,等號左邊的index就是一個變量名。)然後其實現在代碼就是變成了下述代碼的樣子,只是這是在後臺自動做的,我們看不到。這些都是Python解釋器默默地幫我們實現的。

@timmer
index = xxx(index)    # index = wrapper2函數內存地址
def index():
    print('welcome to index page')
    time.sleep(2)

我們看現在的代碼,xxx函數加(),一看到函數名+()就會執行函數,返回上面代碼去看,執行xxx函數幹了兩件事,幹了兩件事,定義了wrapper2函數,然後將wrapper2函數內存地址作爲返回值返回。所以,變量名index是指向wrapper2函數的內存地址。

然後再向上加載timmer裝飾器。函數名沒有加(),則執行@語法。@的作用咱們上面說過了,忘掉的可以再看看。index這個變量名,指向的是wrapper2函數,將index函數名傳入timmer函數,還是做了兩件事,定義wrapper1函數,將wrapper1函數的內存地址作爲返回值返回。這個時候已經變成了這個樣子

index = wrapper1(index) 
index = xxx(index)    # index = wrapper2函數內存地址
def index():
    print('welcome to index page')
    time.sleep(2)

這個時候的第一行中,括號內的index指向的是wrapper2函數的內存地址,等號左邊的index是一個新的變量名。這時的index指向的是wrapper1函數的內存地址。

以上就是裝飾器加載時python解釋器在背後默默幫我們做的事。

執行的時候會按照順序依次執行,先執行wrapper1函數,再執行wrapper2函數,再執行被裝飾函數。

這就是我們上面所說的:

裝飾器的加載順序是:由下而上

裝飾器的執行順序是:由上而下

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