Python 迭代器與生成器:處理無限數據的魔法 (第 12 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Dec 26, 2023
post-thumb

在 Python 中,我們習以為常地使用 for item in list: 來遍歷數據。但你知道這背後發生了什麼嗎?

當你需要處理 100 億筆 數據,或者一個 無限 的數列 (如斐波那契數列) 時,如果用傳統的 list 把所有資料存入記憶體,電腦會直接當機。這時,迭代器 (Iterator)生成器 (Generator) 就是你的救星。

1. 迭代器協議 (Iterator Protocol)

Python 的迭代機制建立在兩個魔術方法之上:

  1. __iter__(): 返回迭代器物件本身。
  2. __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 時,它會:

  1. 暫停 函數執行。
  2. 回傳 yield 後面的值。
  3. 保留 函數當時的狀態 (變數值、執行位置)。

下次再呼叫 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),讓你的程式碼像俄羅斯套娃一樣層層分明!


延伸閱讀

LATEST POST
TAG