攝影或3C

Python 進階技巧:徹底理解 `yield` 與 `yield from`

這篇文章將帶你從最基礎的 `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

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

Recent Posts