[Python小記] 裝飾器怎麼用 ?

chaseSpace-L發表於2018-05-27

裝飾器的含義和作用:    

    裝飾器本質上是一個Python函式,它可以讓其他函式在不需要做任何程式碼變動的前提下增加額外功能,裝飾器的返回值也是一個函式物件。

    它經常用於有切面需求的場景,比如:插入日誌、效能測試、事務處理、快取、許可權校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函式功能本身無關的雷同程式碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的物件新增額外的功能。

    注意:本文重點介紹裝飾器如何使用,如需詳細學習裝飾器原理,推薦劉志軍老師的裝飾器講解

示例場景:

    公司後端同學已經寫好了兩個介面供前端小夥伴使用,這兩個介面沒有做任何限制,可以直接呼叫。有一天老大告訴後端的同學:從現在開始,你們開發的每個介面對外提供呼叫的時候都要做身份驗證,使用user:passwd的方式;驗證不通過就不允許呼叫介面。

     介面示例:

    def get_item_price(name):
        # todo: get data from your DB
        # eg:  data is stored in a dict now.
        _dict = {'apple': 9, 'banana': 6}

        return _dict[name]

    def get_item_num(name):
        # todo: get data from your DB
        # eg:  data is stored in a dict now.
        _dict = {'apple': 300, 'banana': 256}

        return _dict[name]

      介面的功能很簡單,根據商品名查詢對應商品的價格和存貨數量。

      想要一勞永逸的給每個介面輕鬆實現user:passwd驗證的話,裝飾器是不二之選!下面是程式碼實現:

    #裝飾器
    def verifier_decorator(func):

        def wrapper(user,passwd,*args,**kwargs):
            #todo: Read username and passwd from your DB and verify
            #eg: passport is stored in a _dict now.

            pass_dict = {'eli':'123','wang':'456'}

            if (user,passwd) in pass_dict.items():
                print('Success, Your passport has been verified!')
                return func(*args,**kwargs)
            else:
                print('Failed, Your passport is not verified!')
        return wrapper

    # 使用@語法糖給函式呼叫裝飾器
    @verifier_decorator
    def get_item_price(name):
        # todo: get data from your DB
        # eg:  data is stored in a dict now.
        _dict = {'apple': 9, 'banana': 6}

        return _dict[name]

    @verifier_decorator
    def get_item_num(name):
        # todo: get data from your DB
        # eg:  data is stored in a dict now.
        _dict = {'apple': 300, 'banana': 256}

        return _dict[name]

    print(get_item_price('eli','13','apple'))
    #Failed, Your passport is not verified!
    #None

    print(get_item_num('wang','456','apple'))
    #Success, Your passport has been verified!
    # 300

        以上程式碼示例中,我在定義介面函式的時候只寫了一個引數,而呼叫時傳了三個引數卻沒有報錯。

        是因為前兩個引數被裝飾器中的閉包函式接收了,定義介面的時候是這麼寫的:

    @verifier_decorator
    def get_item_num(name):

        這段程式碼等效於(把@那行註釋,在呼叫介面前寫上這行) :

    get_item_price = verifier_decorator(get_item_price)

        當實際呼叫的時候,此介面就非彼介面了,直譯器真正執行的其實是裝飾器中的閉包函式,所以呼叫的時候填多個引數也不會報錯。可以理解為,裝飾器取走了你定義的介面函式,又還了一個“一模一樣”的函式給你。但這個過程中裝飾器又幹了點別的事情。【取走--原基礎上新增其他程式碼--再還回】這一整個過程稱為“裝飾”。

        當然,實際上它並不會還回一個“一模一樣”的函式給你,裝飾的過程中,原函式的元資訊會被閉包函式替換掉:

    def deco(func):
        def wrapper(name):
            '''I am wrapper'''
            return func(name)
        return wrapper

    @deco
    def print_name(name):
        '''I am func'''
        print(print_name.__doc__)
        print(print_name.__name__)
        print(name)
    print_name('eli')
    #I am wrapper
    # wrapper
    # eli

        你如果明白了剛才說的“裝飾”過程,這個地方就很容易理解了。即:真正執行的函式是裝飾器中的閉包函式(wrapper)!

        那應該如何保留原函式的元資訊?

    from functools import wraps #<<<---
    def deco(func):
        @wraps(func)  #<<<---
        def wrapper(name):
            '''I am wrapper'''
            return func(name)
        return wrapper

    @deco
    def print_name(name):
        '''I am func'''
        print(print_name.__doc__)
        print(print_name.__name__)
        print(name)
    print_name('eli')
    #I am func
    # print_name
    # eli

        Python標準庫中的functools的wraps方法會在裝飾器執行的時候將原函式的元資訊一同傳遞給閉包函式,進而使得原函式物件的所有資訊得以保留。


宣告:本文章為個人對技術的理解與總結,不能保證毫無瑕疵,接收網友的斧正。

相關文章