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

Generators และ yield

TypeScript ก็มี generator เหมือนกัน function* คู่กับ yield ทำงานคล้ายกันมากเมื่อมองผ่าน ๆ คือได้ lazy iterator เรียก .next() ได้ และ function หยุดค้างไว้ระหว่าง yield

แต่ใน Python generator ไม่ใช่ฟีเจอร์เฉพาะกลุ่ม เพราะฝังอยู่ทั่ว standard library ทั้ง range(), การวนอ่านไฟล์, zip(), map(), filter() และ module itertools ล้วนคืน lazy iterator ที่ยืนอยู่บน generator protocol ตัวเดียวกัน แม้แต่ async/await ก็สร้างทับ generator อยู่ข้างใน เข้าใจ generator ของ Python ได้ ก็เท่ากับเข้าใจแกนสำคัญของภาษาไปแล้วครึ่งหนึ่ง

generator ของ Python คือ function ใดก็ตามที่มีคำสั่ง yield อยู่ การเรียก function นั้นจะคืนค่า generator object — โค้ดจะยังไม่ทำงานจนกว่าคุณจะวนซ้ำ

TypeScript
// TypeScript generator with function*
function* countUp(start: number, stop: number): Generator<number> {
let current = start;
while (current <= stop) {
yield current;
current++;
}
}
const gen = countUp(1, 5);
for (const val of gen) {
process.stdout.write(val + " ");
}
console.log();
// Infinite generator
function* fibonacci(): Generator<number> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
const firstTen = Array.from({ length: 10 }, () => fib.next().value);
console.log(firstTen);
Python
# Python generator with def + yield
def count_up(start, stop):
current = start
while current <= stop:
yield current
current += 1
gen = count_up(1, 5)
for val in gen:
print(val, end=" ")
print()
# Infinite generator
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
first_ten = [next(fib) for _ in range(10)]
print(first_ten)

ข้อแตกต่างสำคัญที่ควรสังเกต:

  • Python ใช้ def ธรรมดา ไม่มี syntax แบบ function* ขอแค่มี yield โผล่ที่ไหนสักแห่งใน function body function นั้นก็กลายเป็น generator ทันที
  • ลูป for ของ Python ทำงานได้โดยตรงกับ iterable ทุกชนิด รวมถึง generator โดยไม่ต้องเรียก .next() เอง
  • TypeScript ต้องระบุ return type เป็น Generator<T> แต่ Python จะอนุมานให้โดยอัตโนมัติ

generator ของ Python รองรับการส่งข้อมูลสองทาง คุณสามารถส่งค่ากลับเข้าไปใน generator ที่หยุดอยู่โดยใช้ .send() ค่าที่ส่งไปจะกลายเป็นผลลัพธ์ของ expression yield ภายใน generator TypeScript รองรับสิ่งนี้ผ่าน gen.next(value) แต่ฟีเจอร์นี้ไม่ค่อยถูกใช้ใน ecosystem ของ TS

TypeScript
// TypeScript: sending values into a generator
function* accumulator(): Generator<number, void, number> {
let total = 0;
while (true) {
const value = yield total;
if (value === undefined) break;
total += value;
}
}
const acc = accumulator();
acc.next(); // prime the generator
console.log(acc.next(10).value); // 10
console.log(acc.next(20).value); // 30
console.log(acc.next(5).value); // 35
Python
# Python: sending values into a generator with .send()
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
next(acc) # prime the generator (advance to first yield)
print(acc.send(10)) # 10
print(acc.send(20)) # 30
print(acc.send(5)) # 35
def count_up(start, stop):
current = start
while current <= stop:
yield current
current += 1
gen = count_up(1, 5)
for val in gen:
print(val, end=" ")
print()
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
first_ten = [next(fib) for _ in range(10)]
print(first_ten)
อะไรทำให้ function ของ Python กลายเป็น generator?
generator จะ raise exception อะไรเมื่อหมดค่าแล้ว?
gen.send(value) ทำอะไร?
จุดประสงค์ของ yield from คืออะไร?