Python 中的生成器

Python 中的生成器

本文將說明 Python 中的生成器。

YouTube Video

Python 中的生成器

概述

Python 的生成器是一種迭代器,也是用於高效進行重複處理的強大功能。它們可以讓你在處理大量資料時撰寫節省記憶體的程式碼

什麼是生成器?

Python 中的生成器是一種特殊函數,使用 yield 關鍵字定義,一次產生一個值。其特點是暫停執行時會保留狀態,之後可以繼續執行。

yield 的基本用法

yield 是一個關鍵字,返回一個值並同時暫停函數執行

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • 當呼叫時,此函數會返回一個每次產生一個值的生成器對象。
  • 如果在沒有下一個值時調用 next(),將會發生 StopIteration 錯誤。

next()StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • 通過像這樣明確處理 StopIteration 錯誤,可以檢測生成器什麼時候結束。

send(value)

調用 send(value) 會讓生成器恢復運行,並將 value 傳遞到 yield 表達式的位置。傳遞的值可在生成器端作為 yield 表達式的返回值接收。在第一次調用時,send(value) 只能傳送 None,因此必須使用 next()send(None)

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • 通過 send(10),生成器的 yield 會變成返回 10 的表達式,且 10 會被賦值給 x

throw()

調用 throw 會讓生成器恢復運行,並在暫停的 yield 位置拋出異常。你可以在生成器內部處理異常來繼續執行。如果異常未被捕捉,則會向外傳播並結束生成器。

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • 在這段程式碼中,通過調用 throw 來將異常注入生成器。在生成器端,異常被處理並返回 recovered

close()

調用 close() 會終止生成器。在生成器內部,可以使用 finally 進行清理操作。在調用 close() 之後再調用 next()send() 會拋出 StopIteration 錯誤。

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • 這段程式碼展示了調用 close() 會終止生成器,並觸發 finally 區塊的清理過程。

yield from

yield from 是用於委託給子生成器的語法。這是一種在生成器內部調用另一個生成器,並將其所有的值傳遞到外部範圍的簡單方式。

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • 這段程式碼透過 yield from 將所有子生成器的值委託給外部生成器,然後再產生 3

與迭代器的關係

生成器內部實作了 __iter__()__next__(),因此本質上是一種類型的迭代器。因此,它們完全相容於 for 迴圈等可疊代操作。

for 迴圈的結合

在 Python 中,for 迴圈內部自動使用 next() 來取得值。

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

透過這種方式,StopIteration 的處理也是自動化的。

建立無限生成器

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

可以建立無限循環的生成器,但使用時必須小心。

生成器表達式

生成器表達式使用括號編寫,語法與列表推導式相似,可以用來定義生成器。

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

與列表推導式不同,生成器表達式不會將所有元素一次載入記憶體,因此更為節省記憶體。

生成器的錯誤處理

生成器中可能會發生例外狀況。這種情況下,你可以像一般 Python 程式碼一樣使用 try-except

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

在此範例中,針對除以零的情形進行了適當的錯誤處理。

生成器的堆疊追蹤

如果生成器內部發生異常,則在恢復生成器時會引發該異常。

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • 這個生成器首先返回 1。恢復時引發的錯誤會被捕獲並作為錯誤訊息顯示。

生成器的使用範例

按行讀取檔案(適用於大型檔案)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • 此函數使用疊代器逐行讀取文本檔,去除每行的空白字元,並以生成器形式返回,使得大型檔案可以用較低的記憶體消耗來處理。

費波那契數列生成器

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • 此程式碼使用生成器來依序產生小於上限的斐波那契數,並通過 for 迴圈將它們輸出。

應用情境

生成器還可以用於以下情境。

  • 大型 CSV 或日誌檔的序列處理
  • API 分頁
  • 串流資料處理(例如:Kafka、IoT 裝置)

總結

概念 重點
yield 暫停並返回值
生成器函數 內容含有 yield,呼叫時會返回迭代器的函數
優點 節省記憶體,適合處理大量資料集
生成器表達式 提供如 (x for x in iterable) 的簡潔語法

透過生成器,你可以在節省記憶體並保持程式簡潔的同時高效處理大量資料集。

您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。

YouTube Video