Bộ sinh (generator) trong Python

Bộ sinh (generator) trong Python

Bài viết này giải thích về bộ sinh (generator) trong Python.

YouTube Video

Bộ sinh (generator) trong Python

Tổng quan

Generators trong Python là một loại iterator và là tính năng mạnh mẽ để xử lý lặp lại một cách hiệu quả. Chúng cho phép bạn viết mã tiết kiệm bộ nhớ khi xử lý lượng dữ liệu lớn.

Bộ sinh (Generator) là gì?

Một bộ sinh trong Python là một hàm đặc biệt, tạo ra từng giá trị một, được định nghĩa bằng từ khóa yield. Đặc điểm của nó là tạm dừng thực thi nhưng vẫn giữ nguyên trạng thái và có thể tiếp tục sau đó.

Cơ bản về yield

yield là một từ khóa trả về một giá trị và tạm dừng thực thi hàm cùng lúc.

 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
  • Khi được gọi, hàm này trả về một đối tượng generator, tạo ra các giá trị lần lượt.
  • Nếu bạn gọi next() khi không còn giá trị tiếp theo, lỗi StopIteration sẽ xảy ra.

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")
  • Bằng cách xử lý rõ ràng lỗi StopIteration như thế này, bạn có thể phát hiện khi generator đã kết thúc.

send(value)

Gọi send(value) tiếp tục generator và gửi value đến vị trí của biểu thức yield. Giá trị được gửi có thể được nhận ở phía generator như là giá trị trả về của biểu thức yield. Ở lần gọi đầu tiên, bạn không thể gửi bất kỳ giá trị nào ngoài None với send(value), vì vậy bạn phải sử dụng next() hoặc 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
  • Với send(10), yield trong generator trở thành biểu thức trả về 10, và 10 được gán cho x.

throw()

Gọi throw sẽ tiếp tục generator và ném ra một ngoại lệ tại vị trí yield đang tạm dừng. Bạn có thể xử lý ngoại lệ bên trong generator để tiếp tục xử lý. Nếu ngoại lệ không được bắt, nó sẽ lan ra ngoài và generator sẽ kết thúc.

 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"
  • Trong đoạn mã này, throw được gọi để đưa một ngoại lệ vào generator. Ở phía generator, ngoại lệ được xử lý và recovered được trả về.

close()

Gọi close() sẽ kết thúc generator. Bên trong generator, bạn có thể thực hiện dọn dẹp bằng cách sử dụng finally. Gọi next() hoặc send() sau khi gọi close() sẽ sinh ra lỗi 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...
  • Đoạn mã này cho thấy khi gọi close() sẽ kết thúc generator và kích hoạt quá trình dọn dẹp trong finally.

yield from

yield from là cú pháp được sử dụng để ủy quyền cho trình sinh phụ. Đây là một cách đơn giản để gọi một trình sinh khác bên trong một trình sinh và truyền tất cả các giá trị của nó ra phạm vi bên ngoài.

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]
  • Đoạn mã này ủy quyền tất cả các giá trị từ trình sinh phụ sang trình sinh bên ngoài bằng cách sử dụng yield from, sau đó trả về giá trị 3.

Mối liên hệ với Iterator

Generators cài đặt nội bộ __iter__()__next__(), vì vậy chúng là một loại iterator. Do đó, chúng hoàn toàn tương thích với các thao tác lặp như vòng lặp for.

Tích hợp với vòng lặp for

Trong Python, vòng lặp for sử dụng next() bên trong để tự động lấy giá trị.

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

Với cách này, việc xử lý StopIteration cũng được thực hiện tự động.

Tạo generator vô hạn

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

Có thể tạo ra vòng lặp vô hạn bằng generator, nhưng cần thận trọng khi sử dụng.

Biểu thức generator

Biểu thức generator, được viết với dấu ngoặc tròn, cho phép bạn định nghĩa generator với cú pháp giống như 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)

Khác với list comprehension, chúng không nạp tất cả phần tử vào bộ nhớ cùng lúc, giúp tiết kiệm bộ nhớ hơn.

Xử lý lỗi trong generator

Ngoại lệ có thể xảy ra bên trong một generator. Trong những trường hợp như vậy, bạn sử dụng try-except giống như trong mã Python thông thường.

 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

Trong ví dụ này, việc xử lý lỗi được thực hiện thích hợp khi xảy ra phép chia cho 0.

Stack trace của generator

Nếu có một ngoại lệ xảy ra bên trong generator, nó sẽ được ném ra khi generator được tiếp tục thực thi.

 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 này trả về 1 đầu tiên. Lỗi phát sinh khi tiếp tục được bắt và hiển thị dưới dạng thông báo lỗi.

Ví dụ sử dụng generator

Đọc file từng dòng một (phù hợp cho các file lớn)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Hàm này đọc từng dòng của tệp văn bản bằng iterator, loại bỏ khoảng trắng ở mỗi dòng và trả về dưới dạng generator, cho phép xử lý các tệp lớn với mức sử dụng bộ nhớ thấp.

Generator cho dãy Fibonacci

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)
  • Đoạn mã này sử dụng một generator để tạo tuần tự các số Fibonacci nhỏ hơn giới hạn trên và xuất chúng bằng vòng lặp for.

Trường hợp sử dụng

Generator cũng có thể được sử dụng trong các trường hợp sau.

  • Xử lý tuần tự các file CSV hoặc log lớn
  • Phân trang API
  • Xử lý dữ liệu streaming (ví dụ: Kafka, thiết bị IoT)

Tóm tắt

Khái niệm Điểm chính
yield Tạm dừng và trả về một giá trị
Hàm generator Một hàm chứa yield và trả về một iterator khi được gọi
Ưu điểm Tiết kiệm bộ nhớ và lý tưởng cho việc xử lý tập dữ liệu lớn
Biểu thức generator Cho phép cú pháp ngắn gọn như (x for x in iterable)

Bằng cách sử dụng generator, bạn có thể xử lý hiệu quả tập dữ liệu lớn, tiết kiệm bộ nhớ và giữ cho mã nguồn ngắn gọn.

Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.

YouTube Video