顯示具有 Comprehension 標籤的文章。 顯示所有文章
顯示具有 Comprehension 標籤的文章。 顯示所有文章

2026年10月5日 星期一

Python 生成式與產生器:使用函式的經典範例

Python 入門|函式與資料處理

Python 生成式與產生器:使用函式的經典範例

生成式適合用精簡語法建立資料;產生器則能在需要時逐筆提供資料。本文透過函式示範串列、集合與字典生成式,以及使用 yield 和生成器運算式的經典寫法,並比較它們的執行方式。

一、生成式與產生器的差異

比較項目生成式(Comprehension)產生器(Generator)
主要用途用簡潔語法建立或轉換串列、集合、字典。逐筆產生資料,避免預先建立全部結果。
常見寫法[...]、{...}函式中的 yield,或 (...) 生成器運算式。
資料產生時機通常立即建立完整結果。取值時才逐筆產生,常用於迴圈或 next()。
常見情境需要完整結果、索引或多次使用。資料量大,或只需順序處理一次。

可以把生成式理解為「建立資料的語法」,把產生器理解為「逐筆供應資料的物件或機制」。兩者可以搭配函式使用,但用途和執行方式不同。

二、函式搭配生成式

範例 1:回傳平方數串列

def make_squares(numbers):
    return [number ** 2 for number in numbers]

print(make_squares([1, 2, 3, 4, 5]))
# [1, 4, 9, 16, 25]

串列生成式會一次建立所有平方數,函式再用 return 回傳完整串列。

範例 2:篩選偶數

def get_evens(numbers):
    return [number for number in numbers if number % 2 == 0]

print(get_evens(range(1, 11)))
# [2, 4, 6, 8, 10]

生成式尾端的 if 用來篩選資料,只有偶數會放進結果串列。

範例 3:建立平方對照字典

def make_square_map(numbers):
    return {number: number ** 2 for number in numbers}

print(make_square_map(range(1, 6)))
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

字典生成式以冒號分隔鍵和值,適合建立查表資料。

範例 4:取得不重複的文字長度

def unique_lengths(words):
    return {len(word) for word in words}

print(sorted(unique_lengths(["rice", "fish", "corn", "tea"])))
# [3, 4]

大括號中沒有鍵值冒號時,這是集合生成式。集合會自動去除重複值;範例以 sorted() 排序,讓結果容易閱讀。

三、產生器函式:使用 yield

範例 5:逐筆產生平方數

def generate_squares(n):
    for number in range(1, n + 1):
        yield number ** 2

for value in generate_squares(5):
    print(value)
# 1
# 4
# 9
# 16
# 25

包含 yield 的函式稱為產生器函式。呼叫它時會得到產生器物件;每次迴圈取值,函式就執行到下一個 yield,交出一筆資料並暫停。

範例 6:逐筆篩選偶數

def generate_evens(numbers):
    for number in numbers:
        if number % 2 == 0:
            yield number

for value in generate_evens(range(1, 11)):
    print(value)
# 2
# 4
# 6
# 8
# 10

這個產生器不會先建立偶數串列,而是在迴圈需要時交出符合條件的數字。

範例 7:產生倒數數字

def countdown(start):
    while start > 0:
        yield start
        start -= 1

for value in countdown(5):
    print(value)
# 5
# 4
# 3
# 2
# 1

產生器會保留暫停時的區域變數狀態,所以再次取值時,start 會接續前一次的值。

範例 8:逐行讀取文字檔

def read_lines(file_path):
    with open(file_path, encoding="utf-8") as file:
        for line in file:
            yield line.rstrip("\n")

for line in read_lines("data.txt"):
    print(line)

每次只交出一行,適合逐行處理大型文字檔。使用 with 可在讀取結束後自動關閉檔案。

四、函式回傳生成器運算式

範例 9:回傳平方數生成器

def square_stream(numbers):
    return (number ** 2 for number in numbers)

squares = square_stream(range(1, 6))

for value in squares:
    print(value)
# 1
# 4
# 9
# 16
# 25

這個函式回傳的是生成器運算式。圓括號 () 不會立即建立完整串列,而是等到迴圈取值時逐筆計算。

範例 10:使用 next() 手動取值

def make_number_stream(n):
    return (number for number in range(1, n + 1))

numbers = make_number_stream(3)

print(next(numbers))  # 1
print(next(numbers))  # 2
print(next(numbers))  # 3

每次呼叫 next() 會取出下一筆資料。資料全部取完後,再呼叫 next() 會引發 StopIteration;通常使用 for 迴圈會更方便。

五、怎麼選擇適合的寫法?

  • 要一次取得完整結果,並可能重複使用:用串列生成式搭配 return。
  • 要逐筆運算,並保留較複雜的流程狀態:用產生器函式搭配 yield。
  • 只需簡單地逐筆轉換資料:函式可以回傳生成器運算式。
  • 要建立鍵值查表或去除重複項目:分別考慮字典生成式或集合生成式。

重點記憶:生成式搭配 return 常會回傳已建立好的集合;產生器函式用 yield 暫停並逐筆交付資料;生成器運算式則可用簡短語法逐筆產生資料。

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 的函式;但這類寫法不在本文「不使用函式」的範圍內。

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