เจนเนอเรเตอร์ในภาษาไพธอน

เจนเนอเรเตอร์ในภาษาไพธอน

บทความนี้อธิบายเกี่ยวกับเจนเนอเรเตอร์ในภาษาไพธอน

YouTube Video

เจนเนอเรเตอร์ในภาษาไพธอน

ภาพรวม

เจนเนอเรเตอร์ในไพธอนคือชนิดหนึ่งของตัววนซ้ำและเป็นฟีเจอร์ที่ทรงพลังสำหรับการประมวลผซ้ำอย่างมีประสิทธิภาพ ช่วยให้คุณเขียนโค้ดที่ใช้หน่วยความจำอย่างมีประสิทธิภาพเมื่อทำงานกับข้อมูลจำนวนมาก

เจนเนอเรเตอร์คืออะไร?

เจนเนอเรเตอร์ในไพธอนเป็นฟังก์ชันพิเศษที่สร้างค่าทีละค่าด้วยคำสั่ง yield ลักษณะเฉพาะของมันคือหยุดทำงานชั่วคราวแต่ยังคงสถานะเดิมไว้ และสามารถเริ่มต่อได้ในภายหลัง

พื้นฐานของ yield

yield เป็นคำสั่งที่คืนค่ากับหยุดการทำงานของฟังก์ชันในเวลาเดียวกัน

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • เมื่อตัวฟังก์ชันนี้ถุกเรียก จะคืนเจนเนอเรเตอร์อ็อบเจ็กต์ที่ส่งค่าทีละค่า
  • หากคุณเรียกใช้ next() เมื่อไม่มีค่าต่อไป จะเกิดข้อผิดพลาด StopIteration

next() และ StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • โดยการจัดการข้อผิดพลาด StopIteration อย่างชัดเจนเช่นนี้ คุณสามารถตรวจจับได้ว่า generator ทำงานเสร็จแล้วหรือไม่

send(value)

การเรียกใช้ send(value) จะทำให้ generator ทำงานต่อ และส่งค่า value ไปยังตำแหน่งของนิพจน์ yield ค่าที่ส่งสามารถรับได้ในฝั่ง generator เป็น ค่าที่คืนมาจากนิพจน์ yield ในการเรียกใช้ครั้งแรก คุณไม่สามารถส่งค่าอื่นนอกจาก None ด้วย send(value) ได้ ดังนั้นจึงต้องใช้ next() หรือ send(None)

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • เมื่อใช้ send(10) นิพจน์ yield ใน generator จะเป็น นิพจน์ที่คืนค่า 10 และตัวแปร x จะถูกกำหนดค่าเป็น 10

throw()

การเรียกใช้ throw จะทำให้ generator ทำงานต่อ และโยนข้อผิดพลาด ณ ตำแหน่งของ yield ที่หยุดอยู่ คุณสามารถจัดการข้อผิดพลาดภายใน generator เพื่อดำเนินการต่อไปได้ หากไม่มีการจับข้อผิดพลาด ข้อผิดพลาดจะถูกส่งออกไปภายนอกและ generator จะสิ้นสุด

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • ในโค้ดนี้ จะมีการเรียกใช้ throw เพื่อส่งข้อผิดพลาดเข้าสู่ generator ในฝั่ง generator จะมีการจัดการข้อผิดพลาดและคืนค่า recovered

close()

การเรียกใช้ close() จะสิ้นสุดการทำงานของ generator ภายใน generator คุณสามารถดำเนินการล้างข้อมูลโดยใช้ finally การเรียกใช้ next() หรือ send() หลังจากเรียกใช้ close() แล้วจะเกิดข้อผิดพลาด StopIteration

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • โค้ดนี้แสดงให้เห็นว่าการเรียกใช้ close() จะสิ้นสุด generator และเริ่มกระบวนการล้างข้อมูลใน finally

yield from

yield from เป็นไวยากรณ์ที่ใช้สำหรับ มอบหมายให้กับ subgenerator นี่เป็นวิธีง่ายๆ ในการเรียก generator อื่นภายใน generator และ ส่งผ่านค่าทั้งหมดของมันไปยัง scope ภายนอก

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • โค้ดนี้มอบหมายค่าทั้งหมดจาก subgenerator ไปยัง generator ภายนอกโดยใช้ yield from และจากนั้น yield 3

ความสัมพันธ์กับตัววนซ้ำ

เจนเนอเรเตอร์มีการสร้าง __iter__() และ __next__() ภายใน จึงจัดเป็นชนิดหนึ่งของตัววนซ้ำ ดังนั้นจึงสามารถใช้งานกับการวนซ้ำ เช่น for loop ได้อย่างเต็มที่

การใช้งานร่วมกับ for loop

ในไพธอน for loop จะใช้ next() ภายในเพื่อดึงค่าทีละค่าโดยอัตโนมัติ

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

วิธีนี้จะจัดการกับ StopIteration โดยอัตโนมัติด้วย

สร้างเจนเนอเรเตอร์แบบไม่สิ้นสุด

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

สามารถสร้างลูปที่ไม่มีที่สิ้นสุดได้ แต่ต้องระมัดระวังในการใช้งาน

ประโยคเจนเนอเรเตอร์

ประโยคเจนเนอเรเตอร์ เขียนโดยใช้วงเล็บ () ทำให้กำหนดเจนเนอเรเตอร์ด้วยไวยากรณ์คล้ายกับ list comprehension

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

ต่างจาก list comprehension ตรงที่ เจนเนอเรเตอร์จะไม่โหลดทุกค่าลงหน่วยความจำพร้อมกัน ทำให้ประหยัดหน่วยความจำมากกว่า

การจัดการข้อผิดพลาดในเจนเนอเรเตอร์

อาจเกิดข้อยกเว้นขึ้นภายในเจนเนอเรเตอร์ ในกรณีแบบนี้สามารถใช้ try-except ได้เหมือนในโค้ดไพธอนปกติ

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

ในตัวอย่างนี้จะมีการจัดการข้อผิดพลาดหากเกิดการหารด้วยศูนย์

Stack Trace ของเจนเนอเรเตอร์

หากเกิดข้อยกเว้นภายใน generator ข้อผิดพลาดนั้นจะถูกเรียกใช้เมื่อ generator ทำงานต่อ

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • generator นี้จะคืนค่า 1 เป็นอันดับแรก ข้อผิดพลาดที่เกิดขึ้นเมื่อรันต่อจะถูกจับและแสดงเป็นข้อความผิดพลาด

ตัวอย่างการใช้เจนเนอเรเตอร์

อ่านไฟล์ทีละบรรทัด (เหมาะสำหรับไฟล์ขนาดใหญ่)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • ฟังก์ชันนี้อ่านไฟล์ข้อความทีละบรรทัดด้วย iterator ตัดช่องว่างส่วนเกินจากแต่ละบรรทัด และส่งคืนเป็น generator ทำให้สามารถประมวลผลไฟล์ขนาดใหญ่ได้โดยใช้หน่วยความจำต่ำ

เจนเนอเรเตอร์สำหรับลำดับฟีโบนักชี

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • โค้ดนี้ใช้ generator ในการสร้างลำดับตัวเลขฟีโบนัชชีที่น้อยกว่าค่าขีดจำกัดทีละตัว และแสดงผลด้วยลูป for

กรณีการใช้งาน

Generators ยังสามารถนำไปใช้ในสถานการณ์ต่อไปนี้ได้เช่นกัน

  • ประมวลผลไฟล์ CSV หรือ log ขนาดใหญ่แบบทีละบรรทัด
  • การแบ่งหน้าข้อมูล (pagination) ของ API
  • ประมวลผลข้อมูลสตรีมมิ่ง (เช่น Kafka หรืออุปกรณ์ IoT)

สรุป

แนวคิด ประเด็นสำคัญ
yield หยุดและส่งค่ากลับ
ฟังก์ชันเจนเนอเรเตอร์ ฟังก์ชันที่มี yield และจะคืนตัววนซ้ำเมื่อถูกเรียก
ข้อดี ประหยัดหน่วยความจำ เหมาะกับการประมวลผลข้อมูลจำนวนมาก
ประโยคเจนเนอเรเตอร์ สามารถเขียนสั้น ๆ เช่น (x for x in iterable)

ด้วยการใช้เจนเนอเรเตอร์ คุณจะสามารถประมวลผลข้อมูลขนาดใหญ่ได้อย่างมีประสิทธิภาพ ประหยัดหน่วยความจำ และเขียนโค้ดได้กระชับ

คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย

YouTube Video