Decorator

      在〈Decorator〉中尚無留言

Decorator

上一篇的Partial function, 把函數的功能減弱, 那是否也能擴充函數的功能呢.  這就是decorator的工作了.

Decorator中文譯為裝飾器, 但如果按照字面的解譯的話, 則無法理解它的運作機制. 所以本人這裏 把它譯為 “包裝器”, 或 “擴充器” .

網路上有很多Decorator的說明, 講解的都相當的難懂, 甚至是錯誤的. 所以建議直接看本人的說明即可.

首先使用下面的代碼作為說明的範例

def wrapper(f):#包裝器
    def show():#列印函數
        print('開始執行 %s():' % f.__name__)
        print(f())#取得要列印的資料
        print('列印結束')
    return show#傳回列印的函數

def getName():
    return 'Thomas'
def getAge():
    return 18
getName=wrapper(getName)
getName()

getAge=wrapper(getAge)
getAge()

上述有二個函數, 分別是getName(), 及getAge(), 這二個函數只會傳回一個值, 並沒有列印的功能. 所以如果下達 getName(), 則不會有任何結果顯示.

def getName():
    return 'Thomas'
def getAge():
    return 18

那如果要擴充getName()及getAge()讓它們能自已列印呢(當然要擴充的不是只有列印啦, 不然直接寫成print(getName()) 不就得了嗎!). 此時我們就定義一個 wrapper(f), 把這二個函數傳進去, 然後再定義一個show()函數執行要列印的工作. 最後包裝函數把show()函數傳回

def wrapper(f):#包裝器
    def show():#列印函數
        print('開始執行 %s():' % f.__name__)
        print(f())#取得要列印的資料
        print('列印結束')
    return show#傳回列印的函數

接下來就執行wrapper(getName), 把getName()函數傳進去包裝, 然後會返回新的show()函數. 好死不死, 我們又用getName去接收show(), 所以getName就變成了有列印功能的函數了.

那原來傳進去wrapper的getName(), 會消失嗎!! 請注意一件事, 變數即函數, 函數即變數,
而且函數就是物件.
因此傳進wrapper的物件(函數), 被wrapper裏面參考著, 所以並不會消失.

getName=wrapper(getName)
getName()

getAge=wrapper(getAge)
getAge()

簡化

上述的getName=wrapper(getName), 看起來怪怪的, 但實際上是正確的, 只不過getName寫了二次, 實在是煩, 所以就可以在getName及getAge的上方, 使用 “@” 來指定包裝, 代碼如下

def wrapper(f):#包裝器
    def show():#列印函數
        print('開始執行 %s():' % f.__name__)
        print(f())#取得要列印的資料
        print('列印結束')
    return show#傳回列印的函數

@wrapper
def getName():
    return 'Thomas'

@wrapper
def getAge():
    return 18

getName()
getAge()

擴充器傳入參數

傳入wrapper的參數, 除了getName函數之外, 若還想傳入其他參數時, 那該怎麼辦呢? 這就需要再加入一層接收參數的函數 param(). 不過要注意的是, wrapper()要接收一般參數, 而 param() 則要接收函數參數, 如下

def wrapper(txt):#包裝器, 接收一般參數
    def param(f):#接收函數
        def show():#列印函數
            print('%s : 開始執行 %s():' % (txt, f.__name__))
            print(f())#取得要列印的資料
            print('列印結束')
        return show#傳回列印的函數
    return param

@wrapper('Mahaljsp')
def getName():
    return 'Thomas'

@wrapper('Mahaljsp')
def getAge():
    return 18

getName()
getAge()

上面其實是 getName=wrapper(“Mahaljsp”)(getName)的簡寫.

那為什麼可以寫成 wrapper(“Mahaljsp”)(“getName”), 有二個”()” 呢. 原因是wrapper(“Mahaljsp”) 會傳回 param()函數, 然後再把getName放入param()之中

name屬性

上面的getName(), getAge()函數, 本身就是物件, 它們具有 __name__的屬性, 原本 __name__ 的屬性是 getName及getAge. 但一經包裝後, 就都變成了 show了, 可由下面的代碼驗証

print(getName.__name__)
結果 :
show

改變 __name__ 屬性會造成某些依賴函數簽名的代碼出現錯誤, 所以必需把name屬性改回原本的getName, 如下代碼, 只需在show()函數上方加入 @functools.wraps(f) 即可

import functools
def wrapper(txt):#包裝器
    def param(f):
        @functools.wraps(f)
        def show():#列印函數
            print('%s : 開始執行 %s():' % (txt, f.__name__))
            print(f())#取得要列印的資料
            print('列印結束')
        return show#傳回列印的函數
    return param

@wrapper('Mahaljsp')
def getName():
    return 'Thomas'

@wrapper('Mahaljsp')
def getAge():
    return 18

getName()
getAge()
print(getName.__name__)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *