本文 AI 產出,尚未審核

Python 課程 ── 迭代與生成器(Iteration & Generators)

主題:iter()next()


簡介

在 Python 中,迭代是遍歷資料集合(如 list、dict、set、字串)最常見的方式。無論是 for 迴圈、列表生成式,或是自訂的生成器,都在背後依賴同一套「迭代協定」:物件需要提供 __iter__() 方法,返回一個迭代器(iterator),而迭代器則必須實作 __next__() 方法,讓程式在每一次取值時前進到下一個元素。

iter()next() 正是這套協定的核心 API。掌握它們不僅能讓你寫出更具可讀性與彈性的程式碼,還能在需要懶惰求值(lazy evaluation)或大資料流時,避免一次性載入全部資料而造成記憶體壓力。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,直到實務應用,完整呈現 iter() / next() 的使用方式,幫助初學者快速上手,也讓中級開發者在日常開發中能更得心應手。


核心概念

1. 什麼是迭代器(Iterator)?

  • 迭代器是一種遵守「迭代協定」的物件,具備兩個關鍵方法:
    1. __iter__():返回自身(self),使物件本身可被當作迭代器使用。
    2. __next__():返回序列中的下一個元素;若已無元素,拋出 StopIteration

圖示

可迭代物件 (list, dict, set, str, ...) ──► iter() ──► 迭代器 ──► next()

2. iter():把「可迭代物件」轉成迭代器

my_list = [10, 20, 30]
it = iter(my_list)        # 取得 list 的迭代器
print(type(it))           # <class 'list_iterator'>
  • 內建容器(list、tuple、dict、set、str)都有隱藏的 __iter__ 實作,呼叫 iter() 就會得到對應的迭代器。
  • 若物件本身已是迭代器,iter(obj) 會直接返回該物件本身,保持 id 不變

3. next():取得下一個元素

print(next(it))   # 10
print(next(it))   # 20
print(next(it))   # 30
# 再呼叫一次會拋出 StopIteration
  • next(iterator, default) 允許提供 default 值,當迭代結束時返回該值而不拋例外:
    print(next(it, None))   # None,而不是 StopIteration
    

4. 自訂迭代器

如果內建容器無法滿足需求,你可以自行實作一個迭代器類別:

class Countdown:
    """倒數計時器,從 start 逐步遞減到 0"""
    def __init__(self, start: int):
        self.current = start

    def __iter__(self):
        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

程式碼範例

以下提供 5 個實用範例,說明 iter() / next() 在不同情境下的運用。

範例 1:手動遍歷字典的鍵值

data = {'apple': 3, 'banana': 5, 'cherry': 2}
key_iter = iter(data)          # 只遍歷鍵
while True:
    try:
        k = next(key_iter)
        print(f"{k} => {data[k]}")
    except StopIteration:
        break

說明dict 的迭代預設返回鍵,若想同時取得值,可改用 data.items()

範例 2:使用 next() 搭配 default 防止例外

nums = iter([1, 2, 3])
print(next(nums, '沒了'))   # 1
print(next(nums, '沒了'))   # 2
print(next(nums, '沒了'))   # 3
print(next(nums, '沒了'))   # 沒了   ← 不會拋例外

範例 3:自訂無限生成器 + islice 取前 N 個

import itertools

def naturals():
    n = 0
    while True:
        yield n
        n += 1

# 只取前 10 個自然數
for x in itertools.islice(naturals(), 10):
    print(x, end=' ')   # 0 1 2 3 4 5 6 7 8 9

重點naturals() 本身是一個 生成器(generator),本質上也是迭代器。iter() 不是必須的,因為 yield 已經返回迭代器。

範例 4:將檔案逐行讀取,搭配 next() 取得特定行

with open('example.txt', 'r', encoding='utf-8') as f:
    line_iter = iter(f)          # 檔案本身就是可迭代物件
    # 直接跳過前 5 行
    for _ in range(5):
        next(line_iter, None)

    # 取得第 6 行
    sixth_line = next(line_iter, '')
    print('第 6 行內容:', sixth_line.rstrip())

實務意義:在處理大型日誌檔案時,避免一次讀入全部內容,可節省記憶體。

範例 5:自訂「分批」迭代器(Batch Iterator)

class BatchIterator:
    """將一個可迭代物件分割成固定大小的批次"""
    def __init__(self, iterable, batch_size: int):
        self._iter = iter(iterable)
        self.batch_size = batch_size

    def __iter__(self):
        return self

    def __next__(self):
        batch = []
        try:
            for _ in range(self.batch_size):
                batch.append(next(self._iter))
        except StopIteration:
            if not batch:
                raise               # 完全沒資料,直接結束
        return batch

# 使用範例
data = range(1, 11)               # 1~10
for batch in BatchIterator(data, 3):
    print(batch)
# 輸出:
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]

說明:這個迭代器把任意序列切成「批次」返回,常用於 機器學習資料庫批次寫入 等情境。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記捕捉 StopIteration 直接使用 next() 而未處理結束例外,會導致程式崩潰。 使用 try/except,或提供 default 參數。
對同一迭代器多次呼叫 iter() 會得到相同的迭代器,不會重置,可能導致意外的空迭代。 若需要重新遍歷,重新呼叫 iter(original_iterable)
for 迴圈內同時使用 next() 會改變迭代器的內部狀態,導致漏掉元素或重複。 盡量避免混用;若必須,先把迭代器抽離出來,手動控制。
使用 list(iterator) 生成列表 會一次把所有元素載入記憶體,失去「懶惰求值」的好處。 僅在確定資料量小時使用;大資料請考慮 itertools.islice 或生成器。
自訂迭代器忘記返回自身 __iter__ 必須回傳 self,否則 iter(obj) 會得到新迭代器,導致狀態不一致。 確保 def __iter__(self): return self

最佳實踐

  1. 明確使用 default:在不想捕捉例外的情況下,next(it, sentinel) 是最安全的寫法。
  2. 保持迭代器單一職責:每個迭代器只負責「一次遍歷」的邏輯,若需要多種遍歷方式,請建立不同的迭代器類別或使用生成器函式。
  3. 善用 itertools:Python 標準庫提供了大量的迭代工具(chain, repeat, cycle, islice …),可以減少自行實作的錯誤機率。
  4. 記得關閉資源:對檔案、網路串流等可迭代資源,使用 with 語句確保在迭代完成後自動釋放。

實際應用場景

場景 為什麼使用 iter() / next() 範例簡述
大量日誌檔案的即時分析 只需一次讀取一行,避免一次性載入巨檔。 log_iter = iter(open('log.txt')),使用 next(log_iter) 逐行處理。
網路爬蟲的分頁抓取 每次抓取一頁資料,利用迭代器記錄當前頁碼。 自訂 PageIterator(start=1, step=1),每次 next() 取得下一頁 URL。
機器學習的 Mini‑Batch 訓練 大量樣本分批送入模型,減少記憶體佔用。 BatchIterator(dataset, batch_size=64),在訓練迴圈中 for batch in BatchIterator(...):
串流 API(如 Kafka)消費 消費者持續從佇列取出訊息,直到手動停止。 msg_iter = kafka_consumer.__iter__(),使用 next(msg_iter, None) 取得新訊息。
自訂資料結構的遍歷 想讓自己的類別支援 for 迴圈或解構賦值。 TreeNode 實作 __iter____next__,支援前序遍歷。

總結

  • iter() 將任何可迭代物件(list、dict、檔案、生成器)轉換成符合 迭代協定 的迭代器。
  • next() 取得迭代器的下一個元素,若已遍歷完畢會拋出 StopIteration,或回傳提供的 default
  • 透過自訂 __iter__ / __next__,你可以打造 懶惰、記憶體友好 的資料流,適用於大資料、即時處理或分批運算。
  • 常見的錯誤包括忘記捕捉 StopIteration、誤用多次 iter() 以及在 for 迴圈中同時手動呼叫 next()。遵守最佳實踐(使用 default、保持迭代器單一職責、善用 itertools)能讓程式更安全、可維護。
  • 在實務上,iter() / next() 常被用於 檔案逐行讀取、分頁爬蟲、Mini‑Batch 訓練、串流消費 等情境,幫助開發者以最小的記憶體開銷處理龐大或不確定長度的資料。

掌握了這兩個基礎函式,你就能在 Python 中自如地控制資料流的「進」與「出」,寫出既高效又可讀的程式碼。祝你在迭代與生成器的世界裡玩得開心! 🚀