Photo by Elijah O'Donnell / Unsplash

Background

一直以來都沒有把 Decorator 弄得很清楚. 今天就用這篇文章記錄一下. 我在網路上看到別人的講解 & 自己的領悟.

Start

先對講講我對 decorator 的認知. 網路上很多文章都把 decorator 翻譯成裝飾器. 那顧名思義就是要裝是某一種東西. 實際上是裝飾 python 裡面的 function. 那為什麼要裝飾 function 呢? 基本上就是為了要減少重工的部分. 舉個例子比如說你要炒菜(高麗菜, 地瓜葉兩種為例). 那可能兩種菜條理的方式不一樣. 但整體的流程如下: 洗菜 -> 煮菜. 所以我們有兩個 function. 洗菜的 function 內容是一樣的, 主菜的 function 則不同. 所以照程式的寫法我應該只要寫一個洗菜的 function 即可. 當我要煮菜前就呼叫這共用洗菜的 function 就可以了. 那 decorator 的就是為了讓這些共用的 function(洗菜)在實際寫程式時更方便地被使用所產生的. 本身也是個 function 但目的是為了裝飾另外一個 function.

Example

大致上理解 decorator 後, 我們來看一些比較實際的例子. (在 Python 中我們則是使用 @ 當做 Decorator 使用的語法糖符號, 然後 decorator 的傳入的參數會是 function & return 的值也是個 function)

@my_decorator
def my_func(stuff):
    do_things()

那 my_decorator 如上面所提到, 他其實是一個 function. 所以實際上會變成下面的想法:

my_func = my_decorator(my_func) my_func 被當作參數丟進去 my_decorator 中(裝飾的想法), 那 my_decorator 會 return 回一個新的 function 出來. (想法: 一個 function 被裝飾後產生一個新的 function) 那這個新的 function 又會被 assign 到 my_fun 這個變數中.

def use_print(func):

    def wrapper(*args, **kwargs):
        print("wrapper")
        return func(*args, **kwargs)
    return wrapper

@use_print
def foo():
    print("i am foo")

foo()

執行結果如下

$ python3 foo.py
wrapper
i am foo

從結果我們看到我們呼叫的是 foo function 但是 foo function 經過 use_print 的裝飾, 所以會先執行 use_print 裡 wrapper function 的內容再執行 foo function 本身的內容.

Advance

那在使用 Python裝飾器(decorator)的時候,被裝飾後的 function 其實已經是一個新的 function 了. (上面的解釋有提到) 那有的時候我們還是希望被裝飾的 function 還是對應到原本的 function, 所以 Python 中的 functools 中提供了一個叫 wraps的 decorator 來解決這樣的問題, 我們來參考下列的例子:

def use_print(func):

    def wrapper(*args, **kwargs):
        '''wrapper doc'''
        print("wrapper")
        return func(*args, **kwargs)
    return wrapper

@use_print
def foo():
    '''foo doc'''
    print("i am foo")

foo()
print(foo.__name__, foo.__doc__)

基本上我們家了一些 doc的 comment & 最後面多了一個 print 來顯示 function 的資訊, 執行結果如下:

$ python3 foo.py
wrapper
i am foo
wrapper wrapper doc

我們可以看到最後一行 function 的資訊變成 wrapper function 的相關資訊. 那接著我們使用 wraps 這個 decorator 看看:

from functools import wraps
def use_print(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        '''wrapper doc'''
        print("wrapper")
        return func(*args, **kwargs)
    return wrapper

@use_print
def foo():
    '''foo doc'''
    print("i am foo")

foo()
print(foo.__name__, foo.__doc__)

先 import wraps 再加入到 wrapper function 的上方, 看一下執行結果. 變成 foo function 的相關資訊了

$ python3 foo.py
wrapper
i am foo
foo foo doc

ok. 以上就是我對 decorator 的理解!

Reference