本文 AI 產出,尚未審核
Python 課程 ── 迭代與生成器(Iteration & Generators)
主題:iter() 與 next()
簡介
在 Python 中,迭代是遍歷資料集合(如 list、dict、set、字串)最常見的方式。無論是 for 迴圈、列表生成式,或是自訂的生成器,都在背後依賴同一套「迭代協定」:物件需要提供 __iter__() 方法,返回一個迭代器(iterator),而迭代器則必須實作 __next__() 方法,讓程式在每一次取值時前進到下一個元素。
iter() 與 next() 正是這套協定的核心 API。掌握它們不僅能讓你寫出更具可讀性與彈性的程式碼,還能在需要懶惰求值(lazy evaluation)或大資料流時,避免一次性載入全部資料而造成記憶體壓力。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,直到實務應用,完整呈現 iter() / next() 的使用方式,幫助初學者快速上手,也讓中級開發者在日常開發中能更得心應手。
核心概念
1. 什麼是迭代器(Iterator)?
- 迭代器是一種遵守「迭代協定」的物件,具備兩個關鍵方法:
__iter__():返回自身(self),使物件本身可被當作迭代器使用。__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。 |
最佳實踐
- 明確使用
default:在不想捕捉例外的情況下,next(it, sentinel)是最安全的寫法。 - 保持迭代器單一職責:每個迭代器只負責「一次遍歷」的邏輯,若需要多種遍歷方式,請建立不同的迭代器類別或使用生成器函式。
- 善用
itertools:Python 標準庫提供了大量的迭代工具(chain,repeat,cycle,islice…),可以減少自行實作的錯誤機率。 - 記得關閉資源:對檔案、網路串流等可迭代資源,使用
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 中自如地控制資料流的「進」與「出」,寫出既高效又可讀的程式碼。祝你在迭代與生成器的世界裡玩得開心! 🚀