Python Generator 與 Decorator
這篇教學一次處理兩個讓 Python 程式碼「看起來很高級」的特性:Generator(產生器)用 yield 一次吐一筆資料、不佔記憶體;Decorator(裝飾器)用 @ 幫既有函式加功能、不動原始碼。兩者都是面試常客。
Generator:用 yield 一次吐一筆
函式裡用 yield 取代 return,它就變成產生器——呼叫時不會立刻執行,而是隨著 for 迴圈「要一筆、算一筆」:
def countdown(n):
print("開始倒數")
while n > 0:
yield n # 吐出一個值,暫停在這裡
n -= 1
for i in countdown(3):
print(i)
執行輸出:
開始倒數
3
2
1
注意「開始倒數」直到 for 開始要資料才印出——這就是惰性求值(lazy evaluation)。
為什麼需要 Generator:記憶體
比較兩種「產生一百萬個數字」的方式:
import sys
big_list = [i for i in range(1_000_000)] # List:全部放進記憶體
big_gen = (i for i in range(1_000_000)) # Generator:要時才算
print(sys.getsizeof(big_list), "bytes")
print(sys.getsizeof(big_gen), "bytes")
執行輸出(數值依環境略有差異):
8448728 bytes
104 bytes
小括號的 (i for i in ...) 是 generator 運算式,長相跟 List Comprehension 一樣只差括號。處理大檔案逐行讀取(for line in f)正是同一個精神。
Decorator:用 @ 幫函式加功能
裝飾器是「吃一個函式、回傳加工版函式」的函式。經典範例:幫任何函式加上執行計時:
import time
import functools
def timer(func):
@functools.wraps(func) # 保留原函式的名稱與說明
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 執行原函式
print(f"{func.__name__} 花了 {time.time() - start:.4f} 秒")
return result
return wrapper
@timer # 等同 slow_add = timer(slow_add)
def slow_add(a, b):
time.sleep(0.1)
return a + b
print(slow_add(3, 5))
執行輸出(秒數依環境略有差異):
slow_add 花了 0.1001 秒
8
拆解:@timer 把 slow_add 換成 wrapper;wrapper 用第 20 篇的 *args, **kwargs 原封不動轉傳參數,所以任何函式都能被裝飾。
你早就用過的裝飾器
| 裝飾器 | 出處 |
|---|---|
@dataclass | 第 30 篇,自動生成類別方法 |
@functools.wraps | 本篇,寫裝飾器的標配 |
@app.route("/") | Flask/FastAPI 路由(網頁框架) |
@pytest.fixture | pytest 測試框架 |
讀懂「裝飾器=函式的加工器」,這些框架語法就不再神祕。
常見錯誤
1. Generator 用過一次就空了
產生器是「一次性」的,跑完第二次 for 什麼都拿不到。需要重複使用就重新呼叫產生器函式,或一開始就轉成 list()。
2. 裝飾器忘了 return wrapper(或 return result)
timer 忘了 return wrapper:函式直接變 None;wrapper 忘了 return result:被裝飾的函式永遠回傳 None。兩個 return 都要在。
3. 沒加 @functools.wraps
被裝飾函式的 __name__、docstring 會變成 wrapper 的,除錯與文件工具都會錯亂。寫裝飾器時習慣性加上。
總結
Generator:yield 一次吐一筆、惰性求值省記憶體、一次性用完即空;Decorator:@ 語法把函式換成加工版、靠 *args/**kwargs 轉傳、functools.wraps 必加。第四階段到此完成——下一階段進入實戰應用,從 Python 操作 SQLite 資料庫開始,與本站 SQL 教學系列正式會合。
延伸閱讀
- Python 教學(系列目錄)
- Python 函式 (def)
- Python *args 與 **kwargs
- Python for 與 while 迴圈
- Python sqlite3 資料庫操作(下一篇)