
在閱讀開源專案 (如 Flask 或 Django) 時,你一定看過這種寫法:
@app.route("/")
def index():
return "Hello"
那個 @ 符號到底是什麼?這就是 裝飾器 (Decorator)。它允許我們在不修改原函數程式碼的情況下,動態地增加功能。這聽起來像魔法,但其實只是「函數也是物件」特性的應用。
1. 預備知識:函數是第一類物件 (First-Class Object)
在 Python 中,函數 (Function) 就跟整數、字串一樣,可以:
- 被賦值給變數。
- 作為參數傳給另一個函數。
- 作為另一個函數的回傳值。
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
# 高階函數:接收函數作為參數
def greet(func):
greeting = func("Hi, Python")
print(greeting)
greet(shout) # HI, PYTHON
greet(whisper) # hi, python
2. 閉包 (Closures)
在理解裝飾器前,必須先懂閉包。閉包是指一個內部函數 (Inner Function) 記住了外部函數 (Outer Function) 的變數環境。
def outer(msg):
# 這是外部變數
message = msg
def inner():
# 內部函數可以存取外部變數
print(f"Message is: {message}")
return inner # 回傳內部函數本身 (沒加括號喔)
my_func = outer("Hello")
my_func() # 輸出: Message is: Hello
即使 outer 執行完畢了,my_func (也就是 inner) 依然「記得」當時的 message 變數。這就是閉包。
3. 手工打造裝飾器
裝飾器本質上就是一個 接收函數並回傳新函數 的閉包。
假設我們想在執行某個函數前後,自動印出 Log:
def my_logger(func):
def wrapper():
print("--- 開始執行 ---")
func()
print("--- 執行結束 ---")
return wrapper
def say_hello():
print("Hello World!")
# 手動裝飾
decorated_hello = my_logger(say_hello)
decorated_hello()
輸出:
--- 開始執行 ---
Hello World!
--- 執行結束 ---
4. 語法糖:@ 符號
上面的 decorated_hello = my_logger(say_hello) 寫起來太囉唆。Python 提供了 @ 語法糖來簡化這一切。
@my_logger
def say_bye():
print("Bye Bye!")
say_bye()
這段程式碼等同於手動裝飾,但更直觀。
5. 實戰:處理參數與回傳值
上面的 wrapper 沒有接收參數,如果原函數有參數怎麼辦?我們通常使用 *args 和 **kwargs 來轉發所有參數。
範例:函數計時器
這是一個非常實用的裝飾器,用來測量函數執行時間。
import time
import functools
def timer(func):
# 使用 @functools.wraps 保留原函數的 metadata (如 __name__, __doc__)
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # 執行原函數
end_time = time.time()
print(f"函數 {func.__name__} 執行花費: {end_time - start_time:.4f} 秒")
return result # 記得回傳原函數的結果!
return wrapper
@timer
def heavy_computation(n):
"""模擬耗時運算"""
total = 0
for i in range(n):
total += i
return total
print(heavy_computation(1000000))
6. 總結
裝飾器是 Python 中實現 AOP (Aspect-Oriented Programming) 的主要手段。如果你發現有好幾個函數都有重複的「樣板程式碼」 (如權限檢查、Log 記錄、錯誤處理),試著把它們抽離出來寫成裝飾器吧!
本章重點回顧:
- First-class function: 函數可以被傳遞。
- Closure: 內部函數記住外部變數。
- Syntactic Sugar:
@decorator只是func = decorator(func)的縮寫。 - functools.wraps: 寫裝飾器時務必加上,以免原函數的名稱和文檔遺失。
下一章,我們將繼續深入函數式編程,介紹 Lambda, Map, Filter 與 Reduce,讓數據處理流水線化!
延伸閱讀: