本文 AI 產出,尚未審核

Python – 迭代與生成器

單元:迭代與生成器(Iteration & Generators)

主題:for 迴圈的可迭代物件機制


簡介

在 Python 中,for 迴圈是最常用的控制流程之一。它之所以能夠遍歷 列表、字典、集合、字串,甚至自訂的資料結構,全部仰賴「可迭代物件(iterable)」的概念。了解 for 迴圈背後的可迭代機制,不只能寫出更簡潔的程式碼,還能在需要大量資料處理、串流 (stream) 或記憶體受限的環境中,靈活運用 生成器(generator) 來提升效能。

本篇文章將從 可迭代協定(iteration protocol)迭代器(iterator)生成器 三個層面,逐步說明 for 迴圈是如何取得資料、如何停止迭代,以及在實務開發中常見的陷阱與最佳實踐。


核心概念

1. 什麼是可迭代物件?

在 Python 中,只要一個物件實作了 __iter__() 方法,或同時實作了 __getitem__()(且索引從 0 開始),就被視為 可迭代for 迴圈在執行時會先呼叫 iter(obj),這個內建函式會:

  1. 嘗試呼叫 obj.__iter__(),若成功則回傳一個 迭代器(iterator)物件。
  2. __iter__ 不存在,則改用舊式的序列協定:呼叫 obj.__getitem__(0), obj.__getitem__(1) …,直到拋出 IndexError 為止。

迭代器本身必須實作兩個方法:

方法 目的
__iter__() 回傳自身,讓迭代器本身也是可迭代的
__next__() 產生下一個元素,若沒有更多元素則拋出 StopIteration

重點for 迴圈不直接操作資料容器,而是透過 迭代器協定 把資料「逐一」取出。


2. for 迴圈的執行流程

以下以簡化的流程圖說明 for 迴圈的內部機制:

for target in iterable:
    body
  1. iterableiter(iterable) → 取得 iterator
  2. 呼叫 next(iterator) 取得第一個值,賦給 target
  3. 執行 body
  4. 重複第 2 步,直到 next() 拋出 StopIteration

這個機制使得 for 迴圈可以 懶惰求值(lazy evaluation),只在需要時才產生下一個元素,對於大資料集或無限序列特別有用。


3. 常見的可迭代類型

類型 是否可迭代 取得方式
listtupleset iter(obj) 直接返回內建迭代器
dict 預設迭代鍵 (for k in d:),也可 d.items()d.values()
str 逐字元迭代
range 產生整數序列,記憶體友好
自訂類別 ✅(需實作 __iter____getitem__ 依需求返回自訂迭代器
file 物件 逐行讀取,實作了迭代器協定

4. 程式碼範例

下面提供 5 個實用範例,示範不同可迭代物件與自訂迭代器的使用方式。每段程式碼皆附上說明註解,方便初學者快速掌握概念。

範例 1:遍歷字典的鍵、值、項目

# 直接遍歷鍵 (預設行為)
person = {"name": "Alice", "age": 30, "city": "Taipei"}

for key in person:
    print(f"鍵: {key}")          # name, age, city

# 同時取得鍵與值
for key, value in person.items():
    print(f"{key} => {value}")

技巧:若只需要值,使用 person.values();若要同時取得鍵和值,person.items() 最直觀。


範例 2:使用 range 產生大數列,配合 for 懶惰求值

# 產生 0~999_999 的整數序列,且不會一次佔用全部記憶體
for i in range(1_000_000):
    if i % 200_000 == 0:
        print(f"已處理 {i} 筆資料")

說明range 物件本身就是一個 惰性序列,只在 next() 被呼叫時才計算下一個值。


範例 3:自訂可迭代類別(實作 __iter__

class Countdown:
    """倒數計時器,從 start 迭代到 0"""
    def __init__(self, start: int):
        self.start = start

    def __iter__(self):
        # 回傳一個迭代器物件,本例直接回傳自身
        self.current = self.start
        return self

    def __next__(self):
        if self.current < 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

# 使用方式
for n in Countdown(5):
    print(n, end=' ')   # 5 4 3 2 1 0

重點__iter__ 必須回傳 迭代器本身(或其他迭代器),而 __next__ 必須在結束時拋出 StopIteration


範例 4:生成器函式(generator function)簡化迭代器實作

def fibonacci(limit: int):
    """產生前 limit 個 Fibonacci 數字"""
    a, b = 0, 1
    for _ in range(limit):
        yield a          # 產生一個值,暫停函式狀態
        a, b = b, a + b

# 直接在 for 迴圈中使用
for num in fibonacci(10):
    print(num, end=' ')   # 0 1 1 2 3 5 8 13 21 34

優點:利用 yield,Python 會自動為我們建立符合迭代器協定的物件,省去手寫 __iter__ / __next__ 的繁瑣。


範例 5:檔案逐行讀取(避免一次載入整個檔案)

# 假設 data.txt 含有大量文字資料
with open('data.txt', encoding='utf-8') as f:
    for line_number, line in enumerate(f, start=1):
        # 只處理包含關鍵字的行
        if 'Error' in line:
            print(f"[{line_number}] {line.strip()}")

說明file 物件本身即為可迭代物件,for 迴圈會一次讀取一行,極大降低記憶體使用。


常見陷阱與最佳實踐

陷阱 可能的後果 建議的解決方案
直接修改迭代中的容器(如在 forlist.append() 迭代次數不如預期,甚至拋出 RuntimeError 若需修改,先 建立副本for x in lst[:])或改用 列表推導式
忘記 StopIteration 於自訂迭代器 迴圈永遠不會結束,導致程式卡住 必須在 __next__ 中正確拋出 StopIteration
使用 range 產生巨大列表(如 list(range(10**9)) 記憶體瞬間爆炸 直接使用 range 本身或改用 生成器表達式(i for i in range(10**9))
在生成器內部捕捉 StopIteration 會使外層 for 無法正確終止 只在最外層捕捉,或使用 try/except 包住 next() 呼叫
檔案未關閉 檔案資源泄漏,Windows 上可能鎖檔 使用 with 語句 確保自動關閉

最佳實踐小結

  1. 盡量使用內建的可迭代物件list, dict, range, file),它們已最佳化。
  2. 需要大量資料或無限序列時,優先考慮生成器yield 或生成器表達式)。
  3. 保持迭代過程的純粹性:不要在迭代中改變容器結構,若必須改變,請使用副本或分段處理。
  4. 使用 enumeratezipitertools 等工具,可讓多序列同步迭代更簡潔且安全。

實際應用場景

場景 為何使用 for + 可迭代機制 範例簡述
資料清理(CSV、Log) 逐行讀取,避免一次載入整個檔案 for line in open('log.txt'): ...
批次 API 呼叫 產生請求參數的序列,使用 range 或生成器控制速率 for page in range(1, 101): fetch(page)
大數據分析 itertools.islice 配合生成器分批處理 for chunk in itertools.islice(data_gen, 0, 1000): ...
演算法實作(如 BFS、DFS) 使用 dequelist 作為可迭代的工作隊列 for node in queue:
即時串流處理(WebSocket、Kafka) 產生無限資料流,使用 while True: yield def stream(): while True: yield get_message()

總結

  • for 迴圈之所以能遍歷各式各樣的資料結構,核心在於 可迭代協定:只要物件實作 __iter__(或舊式 __getitem__),就能交給 iter() 產生迭代器。
  • 迭代器本身必須提供 __next__,並在結束時拋出 StopIteration
  • 生成器 是建立迭代器的最簡潔方式,yield 讓函式在每次產生值後暫停,實現「懶惰求值」。
  • 在實務開發中,正確使用可迭代物件能 降低記憶體佔用、提升程式可讀性,同時避免常見的迭代陷阱(如在迭代中修改容器)。

掌握了 for 迴圈的可迭代機制後,無論是處理小型清單還是大規模資料流,都能以 簡潔、可靠 的方式撰寫 Python 程式。祝你在未來的專案中玩得開心、寫得更好!