Các bộ sinh trong Python là một cách tiện lợi để tạo ra các bộ lặp. Chúng cho phép chúng ta lặp qua một chuỗi các giá trị, có nghĩa là, các giá trị được tạo ra ngay lập tức và không được lưu trữ trong bộ nhớ, điều này đặc biệt hữu ích cho các tập dữ liệu lớn hoặc các chuỗi vô hạn.
Máy phát trong Python là một loại hàm đặc biệt trả về một đối tượng iterator. Nó có vẻ giống như một hàm Python bình thường vì định nghĩa của nó cũng bắt đầu bằng từ khóa def. Tuy nhiên, thay vì câu lệnh return ở cuối, máy phát sử dụng từ khóa yield.
Cú pháp của hàm generator() như sau −
def generator(): . . . . . . yield obj it = generator() next(it) . . .
Có hai cách chính để tạo ra các bộ sinh (generators) trong Python:
Hàm sinh (generator function) sử dụng câu lệnh 'yield' để trả về các giá trị cùng một lúc. Mỗi khi phương thức __next__() của hàm sinh được gọi, hàm sinh tiếp tục từ nơi nó đã dừng lại, tức là ngay sau câu lệnh yield cuối cùng. Đây là ví dụ về việc tạo hàm sinh.
def count_up_to(max_value): current = 1 while current <= max_value: yield current current += 1 # Using the generator counter = count_up_to(5) for number in counter: print(number)
1 2 3 4 5
Biểu thức generator cung cấp một cách ngắn gọn để tạo ra các generator. Chúng sử dụng cú pháp tương tự như danh sách hiểu nhưng sử dụng dấu ngoặc đơn tức là "{}" thay vì dấu ngoặc vuông tức là "[]".
gen_expr = (x * x for x in range(1, 6)) for value in gen_expr: print(value)
1 4 9 16 25
Chúng ta có thể tạo một bộ sinh và lặp qua nó bằng cách sử dụng vòng lặp 'while' với xử lý ngoại lệ cho ngoại lệ 'StopIteration'. Hàm trong đoạn mã dưới đây là một bộ sinh, nó lần lượt trả về các số nguyên từ 1 đến 5.
Khi hàm này được gọi, nó trả về một iterator. Mỗi lần gọi phương thức next() sẽ chuyển quyền kiểm soát trở lại cho generator và lấy số nguyên tiếp theo.
def generator(num): for x in range(1, num+1): yield x return it = generator(5) while True: try: print (next(it)) except StopIteration: break
1 2 3 4 5
Các hàm bình thường và hàm sinh (generator functions) trong Python phục vụ các mục đích khác nhau và thể hiện những hành vi khác biệt. Hiểu rõ sự khác nhau của chúng là điều cần thiết để sử dụng hiệu quả trong mã của chúng ta.
Một hàm bình thường tính toán và trả về một giá trị duy nhất hoặc một tập hợp các giá trị dưới dạng danh sách hoặc tuple khi được gọi. Khi nó trả về, việc thực thi hàm đã hoàn tất và tất cả các biến cục bộ sẽ bị loại bỏ, trong khi hàm generator trả về các giá trị một lần tại một thời điểm bằng cách tạm dừng và tiếp tục trạng thái của nó giữa mỗi lần yield. Nó sử dụng câu lệnh yield thay vì return.
Trong ví dụ này, chúng ta đang tạo một hàm bình thường và xây dựng một danh sách các số Fibonacci, sau đó lặp qua danh sách bằng cách sử dụng một vòng lặp.
def fibonacci(n): fibo = [] a, b = 0, 1 while True: c=a+b if c>=n: break fibo.append(c) a, b = b, c return fibo f = fibonacci(10) for i in f: print (i)
1 2 3 5 8
Trong ví dụ trên, chúng ta đã tạo một dãy Fibonacci bằng cách sử dụng hàm thông thường. Khi chúng ta muốn thu thập tất cả các số trong dãy Fibonacci vào một danh sách và sau đó danh sách này được duyệt qua bằng một vòng lặp. Hãy tưởng tượng rằng chúng ta muốn dãy Fibonacci kéo dài đến một số lớn.
Trong những trường hợp như vậy, tất cả các số phải được thu thập trong một danh sách cần rất nhiều bộ nhớ. Đây là lúc generator trở nên hữu ích vì nó tạo ra một số duy nhất trong danh sách và cung cấp nó để sử dụng. Đoạn mã sau đây là giải pháp dựa trên generator cho danh sách các số Fibonacci −
def fibonacci(n): a, b = 0, 1 while True: c=a+b if c>=n: break yield c a, b = b, c return f = fibonacci(10) while True: try: print (next(f)) except StopIteration: break
Output
1 2 3 5 8
Một bộ sinh bất đồng bộ (asynchronous generator) là một coroutine trả về một trình lặp bất đồng bộ (asynchronous iterator). Một coroutine là một hàm Python được định nghĩa với từ khóa async, và nó có thể lên lịch và chờ đợi các coroutine và tác vụ khác.
Giống như một bộ phát điện bình thường, bộ phát điện bất đồng bộ trả về các mục gia tăng trong bộ lặp cho mỗi lần gọi đến hàm anext() , thay vì hàm next().
Cú pháp của Bộ phát bất đồng bộ (Asynchronous Generator) như sau −
async def generator(): . . . . . . yield obj it = generator() anext(it) . . .
Đoạn mã sau đây minh họa một bộ tạo coroutine mà trả về các số nguyên tăng dần trong mỗi lần lặp của vòng lặp async for .
import asyncio async def async_generator(x): for i in range(1, x+1): await asyncio.sleep(1) yield i async def main(): async for item in async_generator(5): print(item) asyncio.run(main())
1 2 3 4 5
Bây giờ, hãy viết một bộ sinh bất đồng bộ cho các số Fibonacci. Để mô phỏng một tác vụ bất đồng bộ bên trong coroutine, chương trình sẽ gọi phương thức sleep() trong 1 giây trước khi trả về số tiếp theo. Kết quả là, chúng ta sẽ thấy các số được in trên màn hình sau một khoảng thời gian trì hoãn là một giây.
import asyncio async def fibonacci(n): a, b = 0, 1 while True: c=a+b if c>=n: break await asyncio.sleep(1) yield c a, b = b, c return async def main(): f = fibonacci(10) async for num in f: print (num) asyncio.run(main())
1 2 3 5 8