Hiểu sâu hơn về Generator trong Python

att288
SWE Tieng Viet
Published in
3 min readMay 5, 2019
The beauty of Generator (:

Trong bài viết về sự kháu nhau giữa Iterator và Generator trong Python, mình có giới thiệu sơ qua về Generator trong Python. Nhưng mình nhận thấy Generator là 1 tool khá hữu dụng và có tính ứng dụng rất cao. Vì vậy, mình quyết định viết 1 bài viết riêng về nó.

Ở bài trước, mình đã nhắc đến một khái niệm cơ bản khi làm việc với generator, đấy chính là keyword “yield”. Tuy nhiên, thật sự generator được sử dụng làm gì? Và tính ứng dụng của nó nằm ở đâu?

Hãy bắt đầu với bài toán dãy số Fibonacci:

Output là:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Như bạn thấy ở trên, nhờ việc sử dụng generator mà mình đã tiết kiệm được memory vì mình không cần phải lưu toàn bộ sequence trong bộ nhớ. Và generator function của mình chỉ cần có 2 variables là curr_valnext_val. Kỹ thuật này gọi là lazy evaluation (đơn giản là bạn sẽ chỉ tính toán khi bạn gọi đến nó).

Kỹ thuật này rất thích hợp cho những bài toán mà nếu bạn phải làm những tính toán lớn, nhưng lại không cần thiết phải lưu toàn bộ kết quả vào bộ nhớ (ví dụ như tính dãy Fibonacci với rất lớn), hoặc có những trường hợp mà bạn phải fetch real-time data, và data được gửi dưới dạng chunks chẳng hạn (bạn phải chờ dữ liệu được gửi đến và bạn cũng không thể pre-compute nó trước được). Những trường hợp như thế thì generator là 1 giải pháp rất hữu hiệu.

Hàm .send() trong generator

Ngoài ra, generator còn hỗ trợ 1 hàm rất thú vị, đấy là hàm .send(). Hãy xem nó hoạt động thế nào nhé:

Đầu tiên, mình tạo 1 hàm multiplier(n), nhiệm vụ của nó là trả lại giá trị giai thừa của n. Ví dụ:

print(f"Result = {next(my_generator)}")
print(f"Result = {next(my_generator)}")
print(f"Result = {next(my_generator)}")
==> Output:
i:1, step=1
Result = 1
i:2, step=1
Result = 2
i:3, step=1
Result = 6

Không có gì đặc biệt đúng không? Vì 1! = 1, 2! = 2, 3! = 6. Hãy xem bước tiếp theo nhé:

print(f"Result = {my_generator.send(5)}")==> Output:
i:4, step=5
Result = 120

Chuyện gì đã xảy ra ở bước này @.@? Trong function multiplier() mình viết, có đoạn step = yield result. Đoạn này có nghĩa mình đã là gán giá trị step là giá trị sẽ nhận được khi mình gọi .send() từ generator. Và nếu mình không send gì, thì step = None. Hiểu đơn giản, thì function .send() làm 2 nhiệm vụ:

  • Gửi giá trị cho variable step (step = yield result)
  • Gọi next(iterator) tiếp theo

Như vậy, next(my_generator) cũng chính là my_generator.send(None) phải không? (Bạn có thể chạy thử hàm đấy nếu thấy không tin lắm 😆).

Trong thực tế thì hàm .send() rất hữu dụng, vì bạn có thể thấy là bạn có thể gửi giá trị bên ngoài vào trong process mà bạn đang xử lí.

Kết nối các generators

Python hỗ trợ bạn 1 phương pháp để kết nối các generators rất thú vị. Đấy là dùng yield from <expression> (Trong PEP 380):

Output là:

g1: 0
g1: 1
g1: 2
g1: 3
g1: 4
g2: 0
g2: -1
g2: -2
g2: -3
g2: -4

Như vậy my_gen_master(n) hoạt động như 1 connector để yield được cả từ my_gen_1()my_gen_2(). Cũng khá thú vị phải không?

Vẫn như mọi lần, Happy Pythoning!

--

--