ข้ามไปยังเนื้อหา

Generators

generator คือ function ที่สามารถหยุดการทำงานชั่วคราว แล้วกลับมาทำงานต่อจากจุดที่หยุดไว้ได้ แทนที่จะคำนวณค่าทั้งหมดแล้ว return ออกมาเป็น list generator จะผลิตค่าออกมา ทีละค่า เฉพาะเมื่อถูกร้องขอเท่านั้น สิ่งนี้เรียกว่า lazy evaluation

หัวใจสำคัญคือ keyword yield เมื่อ Python เจอ yield จะหยุด function ชั่วคราว ส่งค่ากลับไปให้ผู้เรียก แล้วแช่แข็ง state ภายในไว้ (ตัวแปรต่าง ๆ และตำแหน่งของ instruction pointer) การเรียก next() ครั้งถัดไปจะทำงานต่อจากบรรทัดนั้นเป๊ะ ๆ

def count_up(start: int, stop: int):
current = start
while current <= stop:
yield current # pause here and emit current
current += 1 # resume here on the next next() call
gen = count_up(1, 4)
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen)) # 4
# next(gen) would now raise StopIteration
ประเด็นfunction ปกติgenerator function
Returnครั้งเดียว ด้วย returnหลายครั้ง ด้วย yield
Stateถูกทิ้งหลัง returnถูกแช่แข็งไว้ระหว่าง yield
Memoryสร้างผลลัพธ์ทั้งก้อนทีละค่า
Typeคือค่าที่ returnเป็น object ชนิด generator

function จะกลายเป็น generator function ทันทีที่มีคำสั่ง yield แม้เพียงตัวเดียว การเรียก generator function จะไม่ทำให้โค้ดทำงานเลย แต่จะ return generator object ออกมาทันที

ทุก generator object ทำตาม iterator protocol นั่นคือมี method __iter__() และ __next__()

  • next(gen) จะเลื่อนไปยัง yield ตัวถัดไปและ return ค่านั้นออกมา
  • เมื่อไม่มีค่าเหลือแล้ว next(gen) จะ raise StopIteration
  • ลูป for จะเรียก next() ให้อัตโนมัติ และจับ StopIteration เพื่อหยุด
def three_words():
yield "Python"
yield "is"
yield "expressive"
# Equivalent — for loop handles next() and StopIteration for you
for word in three_words():
print(word)

เช่นเดียวกับที่ list comprehension สร้าง list generator expression ก็สร้าง generator โดยใช้วงเล็บแทนวงเล็บเหลี่ยม วิธีนี้ประหยัด memory เพราะไม่มีการสร้าง list ขึ้นมาจริง

squares_list = [x ** 2 for x in range(1_000_000)] # allocates a list
squares_gen = (x ** 2 for x in range(1_000_000)) # allocates almost nothing
# Consume lazily
for sq in squares_gen:
if sq > 25:
break

generator expression เข้ากันได้อย่างเป็นธรรมชาติกับ built-in ที่รับ iterable:

total = sum(x ** 2 for x in range(10)) # no brackets needed inside sum()

เนื่องจาก generator เป็นแบบ lazy เราจึงสามารถนิยามลำดับที่ไม่มีวันจบได้ ตราบเท่าที่ฝั่งผู้บริโภคหยุดร้องขอ

def fibonacci():
a, b = 0, 1
while True: # infinite loop — but never blocks
yield a
a, b = b, a + b
from itertools import islice
def fibonacci():
"""Infinite Fibonacci sequence generator."""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Take only the first 10 values — safe because islice stops early
first_10 = list(islice(fibonacci(), 10))
print("First 10 Fibonacci numbers:")
print(first_10)
# Generator expression: squares of even numbers
even_squares = (x * x for x in range(1, 11) if x % 2 == 0)
print("\nSquares of even numbers 1-10:")
for val in even_squares:
print(val, end=" ")
print()
# Demonstrate single-use nature
gen = (x * 3 for x in range(5))
print("\nFirst pass:", list(gen))
print("Second pass:", list(gen)) # exhausted — empty list
# Manual next() / StopIteration
counter = iter(range(3))
print("\nManual iteration:")
print(next(counter))
print(next(counter))
print(next(counter))
try:
print(next(counter))
except StopIteration:
print("StopIteration raised — generator exhausted")
เกิดอะไรขึ้นเมื่อ Python เจอ `yield` ภายใน function?
exception ใดที่ส่งสัญญาณว่า generator ไม่มีค่าให้ผลิตแล้ว?
ข้อได้เปรียบหลักของ generator expression เหนือ list comprehension คืออะไร?
หลังจาก iterate generator จนหมดแล้ว การ iterate ซ้ำอีกครั้งจะให้ผลลัพธ์อย่างไร?