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的函式;但這類寫法不在本文「不使用函式」的範圍內。
記憶口訣:方括號 [] 通常一次做出串列;圓括號 () 的生成器運算式則逐筆供應資料。生成式是一種語法,產生器是一種逐筆產生資料的方式。
沒有留言:
張貼留言