2026年10月5日 星期一

Python 生成式與產生器:差異、觀念與經典範例

Python 入門|資料處理

Python 生成式與產生器:差異、觀念與經典範例

「生成式」和「產生器」名稱相近,初學時容易混淆。簡單來說,生成式是一種撰寫資料轉換的語法;產生器是一種可以逐筆提供資料的物件。本文先比較兩者,再用不定義函式的方式練習串列生成式與生成器運算式。

一、生成式和產生器有什麼不同?

比較項目生成式(Comprehension)產生器(Generator)
它是什麼用精簡語法建立或轉換資料的寫法。依需求逐筆提供資料的物件。
常見形式串列、集合、字典生成式。生成器運算式,或在函式中使用 yield。
資料何時產生通常立即建立完整結果,例如串列生成式。通常在取值時才產生下一筆。
括號辨認串列生成式使用 [];集合和字典生成式使用 {}。生成器運算式使用 ()。
適合情境資料量不大,之後會重複使用或需要索引。資料量大、只需逐筆處理,或不需要保存全部結果。

因此兩者不是完全相同的概念:生成式偏向「怎麼寫出資料轉換」;產生器偏向「資料怎麼逐筆交付」。生成器運算式的語法很像生成式,但產生的是 generator 物件。

二、本文的範例限制:不使用函式

下列程式都不會自行定義函式,也不使用 def 或 yield。原因是 yield 必須寫在函式中;若不使用函式,就改用生成器運算式來示範產生器。Python 內建的 range()、next() 和 iter() 仍可正常使用。

三、經典生成式範例

範例 1:串列生成式——計算平方數

squares = [number ** 2 for number in range(1, 6)]
print(squares)
# [1, 4, 9, 16, 25]

讀法是:從 1 到 5 逐一取出 number,計算平方後放進新串列。

範例 2:加入條件——挑出偶數

evens = [number for number in range(1, 11) if number % 2 == 0]
print(evens)
# [2, 4, 6, 8, 10]

if 放在生成式後段,用來篩選符合條件的資料。

範例 3:轉換文字——統一轉成大寫

words = ["python", "robot", "farm"]
upper_words = [word.upper() for word in words]
print(upper_words)
# ['PYTHON', 'ROBOT', 'FARM']

範例 4:條件式轉換——判斷成績

scores = [85, 59, 72, 40]
results = ["及格" if score >= 60 else "不及格" for score in scores]
print(results)
# ['及格', '不及格', '及格', '不及格']

這裡的 if ... else 是二選一的條件運算式,用來決定每筆資料轉換成什麼;它和上一個範例用來篩選資料的尾端 if 功能不同。

範例 5:字典生成式——建立數字與平方的對照

square_map = {number: number ** 2 for number in range(1, 6)}
print(square_map)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

冒號左側是鍵,右側是值,適合建立查表資料。

範例 6:集合生成式——整理不重複字母

unique_letters = {letter for letter in "banana"}
print(sorted(unique_letters))
# ['a', 'b', 'n']

集合會自動去除重複項目;集合本身沒有固定順序,所以示範時用 sorted() 排序輸出。

範例 7:雙層迴圈——組合座標配對

pairs = [(x, y) for x in [1, 2] for y in ["A", "B"]]
print(pairs)
# [(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]

閱讀順序和巢狀迴圈相同:先固定一個 x,再依序搭配所有 y。

四、經典產生器範例:生成器運算式

範例 8:逐筆取得平方數

square_stream = (number ** 2 for number in range(1, 6))

print(next(square_stream))  # 1
print(next(square_stream))  # 4
print(next(square_stream))  # 9

把方括號改成圓括號,就建立生成器運算式。每次呼叫 next() 取得一筆,取過的值不會自動回到開頭。

範例 9:用 for 逐筆讀取產生器

even_stream = (number for number in range(1, 11) if number % 2 == 0)

for value in even_stream:
    print(value)
# 2
# 4
# 6
# 8
# 10

for 會自動逐筆向產生器取值,直到資料用完。

範例 10:把生成器轉成串列

number_stream = (number for number in range(1, 6))
number_list = list(number_stream)
print(number_list)
# [1, 2, 3, 4, 5]

呼叫 list() 會把產生器的所有資料一次取出並建立串列。轉換完成後,原本的產生器也已經耗盡。

範例 11:逐筆處理大量資料

measurements = (value / 10 for value in range(1_000_000))

for value in measurements:
    # 在這裡逐筆處理 value
    pass

這個例子只示範逐筆提供資料,不會先建立一百萬筆結果串列。若只需要累加,也可以直接交給內建的 sum():

total = sum(value for value in range(1_000_000))
print(total)

五、如何選擇?

  • 需要完整結果、索引或重複使用:選串列生成式,例如 [x * 2 for x in numbers]。
  • 只需要依序處理一次:選生成器運算式,例如 (x * 2 for x in numbers)。
  • 需要產生器暫停後再繼續、封裝多段產生流程:常見作法是寫含 yield 的函式;但這類寫法不在本文「不使用函式」的範圍內。

記憶口訣:方括號 [] 通常一次做出串列;圓括號 () 的生成器運算式則逐筆供應資料。生成式是一種語法,產生器是一種逐筆產生資料的方式。

沒有留言:

張貼留言