這篇文章將帶你從最基礎的 `yield` 開始,一路講到 `yield from` 的應用場景,並解釋為什麼在處理巢狀結構(如多層列表、樹狀目錄、或是 Word 表格)時,你是如此需要它。
—
## 1. 基礎篇:什麼是 `yield`?
在 Python 中,如果一個函式使用了 `return`,它執行完畢就會「銷毀」並回傳結果。但如果使用了 `yield`,這個函式就變成了一個 **生成器 (Generator)**。
**生成器的特性:**
1. **暫停與繼續**:程式執行到 `yield` 時會「暫停」,把值交出去。
2. **省記憶體**:它不是一次把所有東西存成 List,而是「要一個、做一個」(Lazy Evaluation)。
### 範例:做麵包
假設你要做 3 個麵包。
**傳統方式 (return List)**:
先把 3 個麵包全做完,放在一個大籃子裡,一次端給客人。
def make_bread_list():
breads = []
breads.append("麵包 1")
breads.append("麵包 2")
breads.append("麵包 3")
return breads # 一次全部給
for b in make_bread_list():
print(f"客人吃掉了: {b}")
**生成器方式 (yield)**:
做完第 1 個,馬上端出去給客人吃(函式暫停),客人吃完回來要第 2 個,你再繼續做。
# %%
def make_bread_gen():
yield "麵包 1" # 端出去,暫停
yield "麵包 2" # 端出去,暫停
yield "麵包 3" # 端出去,結束
for b in make_bread_gen():
print(f"客人吃掉了: {b}")
## 2. 進階篇:為什麼需要 `yield from`?
當你的生成器邏輯變得複雜,需要**呼叫另一個生成器**幫忙時,問題就來了。
### 情境:外包工廠
假設你是一個總召 (Main Generator),你負責產出產品。但中間有一部分產品,你外包給「分包商 (Sub Generator)」去做。
#### ❌ 舊寫法 (沒有 yield from)
你需要寫一個迴圈,從分包商那裡一個個拿貨,再一個個交給客戶。
def sub_task():
yield "零件 A"
yield "零件 B"
def main_task():
yield "主產品 Start"
# 這裡很麻煩:你需要手動跑迴圈去接分包商的貨
for item in sub_task():
yield item
yield "主產品 End"
# 測試
for i in main_task():
print(i)
**缺點**:如果分包商還有分包商(多層巢狀),你的程式碼充滿了這種轉手的 `for` 迴圈。
#### ✅ 新寫法 (使用 yield from)
`yield from` 就像建立了一條**直通管道**。你告訴 Python:「接下來的時間交給 `sub_task`,它吐出什麼,直接轉傳給最原本的呼叫者,不用經過我的手。」
def sub_task():
yield "零件 A"
yield "零件 B"
def main_task():
yield "主產品 Start"
# 超簡潔!直接把控制權「委派」給子生成器
yield from sub_task()
yield "主產品 End"
# 測試結果一模一樣,但程式碼更乾淨
for i in main_task():
print(i)
## 3. `yield from` 的強大之處:處理樹狀結構
這就是為什麼在 `docx` 案例中,`yield from` 這麼好用。
當結構是 **”樹狀” (Tree)** 或 **”巢狀” (Nested)** 時,
遞迴 + `yield from` 是絕配。
### 範例:拆解多層紙箱
假設我們有很多紙箱,紙箱裡可能有球,
也可能還有小紙箱。我們要一次列出所有的球。
data = [
"球 1",
[
"球 2",
"球 3",
["球 4", "球 5"] # 紙箱裡的紙箱
],
"球 6"
]
def get_all_balls(box):
for item in box:
if isinstance(item, list):
# 遇到紙箱(list),就委派給自己(遞迴)去處理那個紙箱
# 這裡把內層找到的球,直接傳到最外層
yield from get_all_balls(item)
else:
# 遇到球,直接交出去
yield item
print(list(get_all_balls(data)))
# 輸出: ['球 1', '球 2', '球 3', '球 4', '球 5', '球 6']
這就是 `yield from` 的精髓:**委派 (Delegation)** 與 **扁平化 (Flattening)**。
推薦hahow線上學習python: https://igrape.net/30afN





![Python: 資料格式如 List[dict],如何快速將SN加入每一個dict中,以利Excel輸出?如何解包dict? **dict ; 將List[dict]的資料轉為pandas.DataFrame 長什麼樣子? Python: 資料格式如 List[dict],如何快速將SN加入每一個dict中,以利Excel輸出?如何解包dict? **dict ; 將List[dict]的資料轉為pandas.DataFrame 長什麼樣子?](https://i0.wp.com/savingking.com.tw/wp-content/uploads/2024/02/20240208093926_0.png?quality=90&zoom=2&ssl=1&resize=350%2C233)
![Python: pandas.DataFrame()處理雙維度資料,dict跟2D list轉為DataFrame有何差別?如何用index及columns屬性客製化index跟欄位名稱?df.index = [“一”,”二”,”三”,”四”] ; df.columns = 使用.head(n) ; .tail(m) ;取首n列,尾m列; .at[index,欄位名稱] 取單一資料 ; .iat[index,欄位順序] 取單一資料 ; .loc[index,欄位名稱] 取資料 ; .iloc[index,欄位順序];df.iloc[ [0,1],[0,2]])取資料 ; df.iloc[ 0:3,0:2]切片 Python: pandas.DataFrame()處理雙維度資料,dict跟2D list轉為DataFrame有何差別?如何用index及columns屬性客製化index跟欄位名稱?df.index = [“一”,”二”,”三”,”四”] ; df.columns = 使用.head(n) ; .tail(m) ;取首n列,尾m列; .at[index,欄位名稱] 取單一資料 ; .iat[index,欄位順序] 取單一資料 ; .loc[index,欄位名稱] 取資料 ; .iloc[index,欄位順序];df.iloc[ [0,1],[0,2]])取資料 ; df.iloc[ 0:3,0:2]切片](https://i2.wp.com/savingking.com.tw/wp-content/uploads/2022/11/20221111093547_79.png?quality=90&zoom=2&ssl=1&resize=350%2C233)
![Python: 如何使用 pydub (dub:配音)將m4a 轉換為wav? 用 os.environ [ “PATH” ] 設定環境變量; from pydub import AudioSegment Python: 如何使用 pydub (dub:配音)將m4a 轉換為wav? 用 os.environ [ “PATH” ] 設定環境變量; from pydub import AudioSegment](https://i0.wp.com/savingking.com.tw/wp-content/uploads/2024/09/20240905141103_0_95957e.png?quality=90&zoom=2&ssl=1&resize=350%2C233)


近期留言