Generator đã là một phần quan trọng của Python kể từ khi được giới thiệu với PEP 255.
Generator trong Python là các routine đặc biệt có thể dùng để kiểm soát hành vi lặp của một vòng lặp. Một generator tương tự như một hàm trả về một mảng. Generator có tham số, chúng ta có thể gọi và nó tạo ra một chuỗi số. Nhưng khác với hàm, hàm trả về toàn bộ mảng, generator yield (trả về) một giá trị tại một thời điểm, điều này yêu cầu ít bộ nhớ hơn.
Bất kỳ hàm Python nào có từ khóa "yield" có thể được gọi là generator. Một hàm Python bình thường bắt đầu thực thi từ dòng đầu tiên và tiếp tục cho đến khi gặp câu lệnh return hoặc ngoại lệ hoặc cuối hàm, tuy nhiên, bất kỳ biến cục bộ nào được tạo trong phạm vi hàm đều bị hủy và không thể truy cập thêm. Trong khi trường hợp generator khi gặp từ khóa yield, trạng thái của hàm bị đóng băng (frozen) và tất cả biến được lưu trong bộ nhớ cho đến khi generator được gọi lại.
Chúng ta có thể dùng generator cùng với một iterator hoặc có thể gọi tường minh sử dụng từ khóa "next".
Nói chung generator trong Python:
- Được định nghĩa với từ khóa def
- Sử dụng từ khóa yield
- Có thể chứa nhiều từ khóa yield.
- Trả về một iterator.
Generator với Iterator
def generator_thr_iter():
yield 'xyz'
yield 246
yield 40.50
for i in generator_thr_iter():
print(i)
Kết quả
xyz
246
40.5
Generator sử dụng next
def generator_thr_iter():
yield 'xyz'
yield 246
yield 40.50
>>> g = generator_thr_iter()
>>> g.__next__()
'xyz'
>>> g.__next__()
246
>>> g.__next__()
40.5
>>> g.__next__()
Traceback (most recent call last):
File "", line 1, in
g.__next__()
StopIteration
Chúng ta có thể coi generator như cái trả về nhiều mục một tại một thời điểm thay vì tất cả cùng lúc và hàm generator bị tạm dừng cho đến khi mục tiếp theo được yêu cầu.
Chương trình in bình phương các số từ 1 đến n
Xét trường hợp chúng ta muốn tính bình phương số từ 1 đến n, với n là số rất lớn, sao cho việc tạo danh sách các số lên đến 'n' sẽ chiếm toàn bộ không gian bộ nhớ hệ thống.
Không dùng generator, cách tiếp cận sẽ giống như:
>>> n = 200000000000
>>> number_list = range(1, n+1)
>>> for i in number_list:
print(i*i)
Cách tiếp cận trên sẽ tiêu tốn nhiều bộ nhớ hệ thống. Cách tốt hơn là lặp qua các số mà không bao giờ tạo danh sách các số để bộ nhớ hệ thống không bị chiếm dụng. Đây là lúc generator xuất hiện.
Chương trình generator của chúng ta cho cùng vấn đề sẽ là:
def num_generator(n):
num = 1
while True:
yield num
if num == n:
return
else:
num += 1
for i in num_generator(200000000000):
print(i*i)
Vì vậy trong cách tiếp cận trên, khi vòng lặp for lần đầu được khởi tạo, num_generator được gọi và giá trị n = 200000000000 được lưu trong bộ nhớ và num=1 được khởi tạo và vào vòng lặp while lặp vô hạn. Sau đó gặp yield num, tại thời điểm này vòng lặp while bị đóng băng và tất cả biến cục bộ được lưu trong bộ nhớ. Vì num=1, yield num được trả về cho vòng lặp for và gán cho i, nơi 1(i*i) được in và lời gọi tiếp theo đến num_generator được thực hiện.
Bây giờ thực thi bắt đầu từ điểm nơi nó đã đóng băng trước đó, nên nó thực thi dòng num == n (1 == 200000000000), là false nên num +=1 được thực hiện thành num = 2 và vòng lặp while được thực thi một lần nữa và quá trình tiếp tục.
Cuối cùng vòng lặp while được thực thi cho đến n=200000000000, khi 200000000000 được yield thì dòng tiếp theo 'num == n' (200000000000 == 200000000000) được thực thi, vì nó là true nên câu lệnh return được thực thi.
Vì vậy khi generator thực thi câu lệnh return hoặc gặp ngoại lệ hoặc đạt cuối generator, ngoại lệ "StopIteration" được ném ra và vòng lặp for dừng lại tại thời điểm đó. Vì vậy ở trên chúng ta có thể in bình phương số lên đến 200000000000 mà không bao giờ tạo danh sách lớn các số mà sẽ chiếm bộ nhớ hệ thống lớn.
Xét tình huống trên, chúng ta có thể dùng generator trong thực hành lập trình hàng ngày để tạo chương trình hiệu quả hơn.