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 暫停並逐筆交付資料;生成器運算式則可用簡短語法逐筆產生資料。

沒有留言:

張貼留言