---
# System prepended metadata

title: Decorator 概念簡介

---

# Decorator 概念簡介

考量到 decorator 的概念不好懂，def def 的語法對於從 compiling language 或者是不懂 functional programming 的人可能會覺得突兀或陌生，進而產生誤解，因此簡單打了這篇。如有錯誤敬請各位勇敢指正（但也請確定並事先驗證自己的說法），感謝大家～

一開始學 decorator 時，常常會看到類似下面的這種例子

```python=
def a_function_wrapper(inner_function):
    def wrapper(*args, **kwargs):
        print("The function starts!")
        returned_values = \
            inner_function(*args, **kwargs)
        print("The function ends!")
        return returned_values
    return wrapper

@a_function_wrapper
def original_function():
    print("I run normally~")

if __name__ == "__main__":
    original_function()
```

第一次看到這種連續兩個 def 又 args 星號來星號去的，對於初學者可能不免感到害怕。（事實上我自己也是學了 Python 後過將近一年才懂得這個概念的，還是因為同時學了其他程式語言有類似概念才觸類旁通理解的 :cry:）

為了讓大家好理解，先從一個 scenario 開始考慮起～

## 為什麼我們需要 decorator 呢？

簡單說是這樣的，考慮以下情境
:::info
為了確認程式有沒有開始或完成一個函數，需要在每個函數前後做一些事情⋯⋯（例如：查看當下時間確認執行所需的時間長短、確認在執行哪個函數因此需要查看函數名稱等）
:::

在還沒學 decorator 前，我們可能需要這樣

```python=
def i_am_a_dog():
    print("I am a dog.")
    
def adder(x, y):
    return x + y

if __name__ == "__main__":
    # 「好麻煩！」那幾行都是為了這情境加上去的，\
    #     本來只要中間那行
    print("Function starts!")  # 好麻煩！
    i_am_a_dog()
    print("Function ends!")    # 好麻煩！
    
    print("Function starts!")  # 好麻煩！
    print(isadder(3, 8))
    print("Function ends!")    # 好麻煩！
```

為此我們可能需要複製貼上很多行，想想看今天如果有辦法說==在每個 function 前面與後面（不改動中間的部分）加上一些動作==，我們是不是就可以達到以上的目的了呢？

先拿第一個 `i_am_a_dog` 開刀，原本縮排的內容是
```python
print("I am a dog.")
```
現在變成
```python
print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
print("I am a dog.")
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！
```

因此這個手術過程，可以改寫成
```python
print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
i_am_a_dog()
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！
```
對，這跟前面做的一樣，但我們就可以寫成更 general 的版本
```python
function_to_be_changed = i_am_a_dog
print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
function_to_be_changed()
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！
```
神奇的就在這裡：**function 本身是可以當成一個變數看待的**

### functions as variables

這個概念各位可以想成
```python
def func(a, b, c):
    # do something
    pass
```
的過程，其實就只是
```python
func = ...
```
的一種特例罷了，如果有懂 lambda 的朋友，可以更直接的想成就是
```python
func = lambda a, b, c: pass
```
這樣，但是 lambda 只限制一行（不知道各位看到這邊會不會更懂 lambda 在幹嘛了？）

### 傳遞參數的本質

另一件事，也希望向各位澄清：**function 的 pass in 跟 return 其實就只是兩行「=」（assignment）**，這在幾乎所有程式語言都適用！
也就是說

```python
def adder(x, y):
    return x + y

c = adder(3, 4)  # 注意我！我等等會被解體 Q_Q
```
就是
```python
x, y = 3, 4  # 我就是函數括號裡的東西啦！
# 接著函數到 return 以前的東東，但 adder 沒有
c = x + y  # 原本函數的位子就換成 return 後面接的東西囉～
```
那為什麼要這樣包起來又拆開呢？==程式設計有個三次原則：一個東西要重複三次以上，就包成函數吧！== 函數又叫「副程式」，本身可以視為一連串程式碼的包裝，方便我們把類似的過程包起來用簡單的指令表達。試想如果 adder 裡面有 300 行，然後我們要執行 10 次，而這 10 次根本只差在 x 跟 y 不同，那包成 function 豈不是方便許多？

### ___

好了！到此各位應該知道要怎麼做了吧～
我們只要「再定義一個 function，把需要被處理的 function 看成一般變數（和其他 x, y 那些沒兩樣），傳進 function 對其開刀，再放回原位」就好了！
```python
function_to_be_changed = i_am_a_dog  # --> 這邊變成 \
                                     # 新函數的 input

print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
function_to_be_changed()
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！

i_am_a_dog = function_to_be_changed  # --> 這邊是 \
                                     # 放回原位的動作
```

包成 function 囉～
```python
def function_wrapper(function_to_be_changed):
    print("Function starts!")  # 好麻煩！
    # vvvvvv 注意下面都不會動！ vvvvvv
    function_to_be_changed()
    # ^^^^^^ 注意上面都不會動！ ^^^^^^
    print("Function ends!")    # 好麻煩！

    return function_to_be_changed  # --> 很重要！這邊 \
                                   # 是放回原位的動作！

i_am_a_dog = function_wrapper(i_am_a_dog)
```

本來事情到這邊就結束了，但是當我們想對第二個函數做一樣的事情時，怪事就發生了！
```python
function_to_be_changed = adder  # --> 這邊變成 \
                                     # 新函數的 input

print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
function_to_be_changed()
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！

adder = function_to_be_changed  # --> 這邊是 \
                                     # 放回原位的動作
```

奇怪？本來的 x 跟 y 該放到哪裡去呀？
這邊要再介紹一個技巧： *args 和 **kwargs

### *args 用法

如果今天函數需要傳入的參數數量是 **不固定的**，這時各位也許看過但從不理解的「\*」就可以來當救星了！其主要的語法意義為「打包」（即 JavaScript 中的「...」）
各位可以這樣看
```python
x = 3, 4, 5
```
這樣 x 會變成一個 tuple「(3, 4, 5)」，同樣的事情如果要在 function 實現，第一招可以使用 list 或 tuple 達成
```python
def variable_input(x):
    for i in x:
        print(i)

variable_input([3, 4, 5])  # 用 list
variable_input((3, 4, 5))  # 用 tuple
```
但這樣寫似乎沒有真正達成「參數數量可變」的目的（本質上還是一個參數），因此使用「*」可以改寫成
```python
                         # 自動幫你把所有 inputs \
def variable_input(*x):  # 打包成 tuple `x`
    for i in x:
        print(i)

variable_input(3, 4, 5)
```

（\*\*kwargs 是對 func(x=3, y=4) 這種情況設計的，容我以後有機會再討論，但原理其實相似）

### ___

所以說，為了應付這種狀況，我們需要改成這樣

```python
args = x, y                 # 這些是 \
                            # function_to_be_changed \
                            # 的參數們
function_to_be_changed = adder  # --> 這邊變成 \
                                # 新函數的 input

# ----------- 以下是新函數的內部 ----------- #

print("Function starts!")  # 好麻煩！
# vvvvvv 注意下面都不會動！ vvvvvv
output = function_to_be_changed(*args)
# ^^^^^^ 注意上面都不會動！ ^^^^^^
print("Function ends!")    # 好麻煩！

# ----------- 以上是新函數的內部 ----------- #

wrapper_output = output         # 內部的 output， \
                                # 不知道塞哪裡欸？
adder = function_to_be_changed  # --> 這邊是 \
                                # 放回原位的動作
```

為了應付 args 和 wrapper_output 不知道該放哪裡，如果今天這個 
```
adder = new_function(adder)
```
可以更彈性就好了⋯⋯既然中間都要做一堆事，那定義成 function 是不是就解決了？像這樣

```python
args = x, y                 # 這些是 \
                            # function_to_be_changed \
                            # 的參數們
function_to_be_changed = adder  # --> 這邊變成 \
                                # 新函數的 input

# ----------- 以下是新函數的內部 ----------- #

def some_process(*args):
    print("Function starts!")  # 好麻煩！
    # vvvvvv 注意下面都不會動！ vvvvvv
    output = function_to_be_changed(*args)
    # ^^^^^^ 注意上面都不會動！ ^^^^^^
    print("Function ends!")    # 好麻煩！
    return output

# ----------- 以上是新函數的內部 ----------- #

adder_new = some_process  # --> 這邊是 \
                          # 放回原位的動作
                                
# 要把東西塞進 adder_new 就直接
wanted_output = adder_new(*args)  
# 這樣因為 adder_new 就是 some_process
# 所以便會啟動所有包裝過的動作，
# 然後把 output 放在 wanted_output，達到目標
```

娃～看起來好可怕呀～再直接把新函數寫出來就變這樣囉～

```python
args = x, y
def new_function(function_to_be_changed):

    # ----------- 以下是新函數的內部 ----------- #

    def some_process(*args):
        print("Function starts!")  # 好麻煩！
        # vvvvvv 注意下面都不會動！ vvvvvv
        output = function_to_be_changed(*args)
        # ^^^^^^ 注意上面都不會動！ ^^^^^^
        print("Function ends!")    # 好麻煩！
        return output

    # ----------- 以上是新函數的內部 ----------- #

    return some_process  # --> 這邊是 \
                         # 放回原位的動作

adder_new = new_function(adder)
# 要把東西塞進 adder_new 就直接
wanted_output = adder_new(*args)  
# 這樣因為 adder_new 就是 some_process
# 所以便會啟動所有包裝過的動作，
# 然後把 output 放在 wanted_output，達到目標
```

這就是兩個 def 的由來了，「為了讓 function 可以回傳 function」
那由於舊的 adder 不再被需要了，最後兩行可以直接改成
```python
adder = new_function(adder)  # <-- 注意！我之前出現過！
# 要使用時：
# >>> 方法一
wanted_output = adder(*args)  # 這跟沒經過包裝沒兩樣
# >>> 方法二
wanted_output = adder(x, y)  # 不用多個 args 也可以～
```

於是觀察這兩行
```python
i_am_a_dog = new_function(i_am_a_dog)
adder = new_function(adder)
```
同樣的語法有些累贅，Python 這時提供了一個 **語法糖** 就是 **decorator**，我們只要把原本的函數定義加上 `@xxxx` 就可以少掉這行！

```python
@new_function
def i_am_a_dog():
   # ...
   pass
```

這就是 decorator 的由來囉～
簡單把握兩個原則——
:::info
decorator 是一個函數，輸入是函數，輸出也是函數
:::

然後
:::danger
為了讓輸入的（內）函數可以保有輸入輸出值的成為（外）輸出回傳，因此會有第二層 def 來代表那個被輸入的（內）函數，然後對其進行處理後輸出。
:::
> 完蛋了上面像繞口令⋯⋯簡單說
> ```python
> def 被包裝函數():
>    pass
>
> def 裝飾器(某函數):
>    # something
>    def 替身函數():  # <-- 作為`被包裝函數`的替身
>       某函數()
>    # something
>    return 替身函數
> ``` 

有興趣了解更深入的可以去探索「closure（閉包）」這個概念，但其實只要把握上面兩個原則應該就行了。
（JavaScript 用到超多的想到快死了 T_T）