裝飾器是Python提供的語法糖,常常用來將函式加入一些固定功能
也會拿來將函式註冊成特殊功能


基本語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def decorator(func):
def wrapper():
print("開始執行")
result = func()
print("結束執行")

return result
return wrapper

@decorator
def a_function():
print("這是一個函數")

a_function()

回傳結果:

1
2
3
開始執行
這是一個函數
結束執行

讓我來解說一下這是如何運作的:

解說

首先,你可以將裝飾器理解為一個「有固定形式」的函數工廠
可以將你的函數加上一些固定的功能

外部傳入

當你在你的函數上加上@decorator的時候,你做的事是將a_function函數物件傳進decorator()
所以以下兩種寫法是等價的

1
2
3
4
5
6
7
8
9
10
11
12
def decorator(func):
def wrapper():
print("開始執行")
result = func()
print("結束執行")

return result
return wrapper

@decorator
def a_function():
print("這是一個函數")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def decorator(func):
def wrapper():
print("開始執行")
result = func()
print("結束執行")

return result
return wrapper

def a_function():
print("這是一個函數")

# 把自己傳入裝飾器函數內
a_function = decorator(a_function)

函數內部

有了基本理解,我們來說說decorator函數內是怎麼運作的
首先,他接收了一個函數物件
別忘了,Python是物件導向的語言,連函數也是一個物件,所以當然可以當作參數傳入另一個函數

接著,細看函數內部:

1
2
3
4
5
6
def wrapper():
print("開始執行") # 做自己的事
result = func() # 原本的函數執行
print("結束執行") # 做自己的事

return result

這個函數會先執行自己的動作(開始執行、結束執行)
然後在恰當的位置去執行原本被傳進來的函數,再把原本函數的結果傳回去

所以基本上函數執行結果不被影響,而是另開一個函數
看起來像是修改了a_function(),但實際上是另外開一個函數做處理

最後看整個裝飾器

1
2
3
4
5
6
7
8
def decorator(func):
def wrapper():
print("開始執行")
result = func()
print("結束執行")

return result
return wrapper

decorator最後會返回我們自己另外開的函數
所以最後你執行a_function()時,實際上是執行修改過的wrapper()

參數

如果我們裝飾的a_function會需要參數怎麼辦?
這時候我們可以善用*args**kwargs
這兩個用法可以讓我們接收一整串沒有對應名稱的參數,或是有對應名稱的參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def decorator(func):
def wrapper(*args, **kwargs): # 搜集原本函數的參數
print("開始執行")
result = func(*args, **kwargs) # 執行的時候把整份參數給原本的函數
print("結束執行")

return result
return wrapper

@decorator
def a_function():
print("這是一個函數")

a_function()

範例

此處以執行時印出時間來做範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 引入時間模組來取用當前時間
from datetime import datetime as dt

def startTime(func):
def wrapper(*args, **kwargs):
print(f"開始時間:{dt.now()}")
result = func(*args, **kwargs)
return result
return wrapper

@startTime
def func1():
print("執行func1")

func1()

輸出結果:

1
2
開始時間:2024-12-20 11:55:31.696425
執行func1

由此方法,我們可以很有效率的為多個函數做額外處理
比如說印出開始執行的時間、開始執行時記數等等
而且裝飾器可以不只使用一次,能夠在多個函數都需要相同動作時高效率的工作
也像開頭提到的,有些套件會以此來註冊函數來執行特殊動作

如flask會用@app.route來註冊成一個路徑
Discord也會愈用@bot.command()來註冊成指令