
在 Python 中,我們習以為常地使用 for item in list: 來遍歷數據。但你知道這背後發生了什麼嗎?
當你需要處理 100 億筆 數據,或者一個 無限 的數列 (如斐波那契數列) 時,如果用傳統的 list 把所有資料存入記憶體,電腦會直接當機。這時,迭代器 (Iterator) 與 生成器 (Generator) 就是你的救星。
1. 迭代器協議 (Iterator Protocol)
Python 的迭代機制建立在兩個魔術方法之上:
__iter__(): 返回迭代器物件本身。__next__(): 返回下一個資料。如果沒有資料了,拋出StopIteration異常。
任何實作了這兩個方法的物件,就稱為 迭代器。
手刻一個迭代器
讓我們模仿 range() 函數,寫一個產生數字範圍的迭代器:
class MyRange:
def __init__(self, start, end):
self.value = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.value >= self.end:
raise StopIteration
current = self.value
self.value += 1
return current
# 使用它
nums = MyRange(1, 4)
for n in nums:
print(n) # 1, 2, 3
其實 for 迴圈的本質就是不斷呼叫 next() 並自動處理 StopIteration 異常。
2. 生成器 (Generators):簡化版的迭代器
如果要寫上面的 Class 才能迭代,太麻煩了!Python 提供了 生成器,讓我們用函數的寫法輕鬆實現迭代器功能。
關鍵字是 yield。
2.1 yield 的運作原理
當函數執行到 yield 時,它會:
- 暫停 函數執行。
- 回傳
yield後面的值。 - 保留 函數當時的狀態 (變數值、執行位置)。
下次再呼叫 next() 時,函數會從上次暫停的地方繼續執行。
def my_range_gen(start, end):
current = start
while current < end:
yield current # 暫停並給出值
current += 1
# 使用方式完全一樣
for n in my_range_gen(1, 4):
print(n)
程式碼是不是瞬間少了一半?而且可讀性更高!
3. 為什麼要用生成器?(Lazy Evaluation)
生成器的最大優勢是 惰性求值 (Lazy Evaluation):它只有在被索取時才產生數據,而不是一次產生所有數據。
記憶體大對決:List vs Generator
假設我們要計算 1 到 1000 萬的平方和:
import sys
# 方法 A: List Comprehension (立即產生所有數據)
# 這會在記憶體中建立一個包含 1000 萬個整數的列表
nums_list = [x**2 for x in range(10000000)]
print(f"List 佔用記憶體: {sys.getsizeof(nums_list) / 1024 / 1024:.2f} MB")
# 輸出: 約 380 MB (視系統而定)
# 方法 B: Generator Expression (使用小括號)
# 這只是一個生成器物件,完全不佔用資料空間
nums_gen = (x**2 for x in range(10000000))
print(f"Gen 佔用記憶體: {sys.getsizeof(nums_gen)} Bytes")
# 輸出: 約 112 Bytes (幾乎為 0)
如果你只是要遍歷數據 (例如 sum(nums) 或是寫入檔案),絕對應該使用生成器,這能讓你的程式處理大數據時依然輕盈。
4. 進階技巧:yield from
當你需要從包含另一個生成器的生成器產生值時 (好繞口),可以使用 yield from。它負責將工作「委派」給子生成器。
def sub_gen():
yield 'A'
yield 'B'
def main_gen():
yield 1
# 委派給 sub_gen,直到它用完為止
yield from sub_gen()
yield 2
for item in main_gen():
print(item, end=' ')
# 輸出: 1 A B 2
這在遞迴遍歷樹狀結構 (Tree Traversal) 時非常有用。
5. 總結
生成器是 Python 最強大的特性之一,也是區分 Python 新手與老手的指標。
本章重點回顧:
- Iterator: 實作了
__iter__和__next__的物件。 - Generator: 使用
yield的函數,是簡化版的迭代器。 - Lazy Evaluation: 用空間換時間 (也不算換時間,通常一樣快),極度節省記憶體。
- Generator Expression:
(x for x in data)比[x for x in data]省記憶體。
下一章,我們將學習能夠「修改函數的函數」——裝飾器 (Decorators),讓你的程式碼像俄羅斯套娃一樣層層分明!
延伸閱讀: