Generator trong Python
Trong hướng dẫn này chúng ta cùng đi tìm hiểu xem Generator trong Python là gì? Lý do vì sao bạn nên dùng Python Generator trong lập trình Python
Để đi tìm hiểu về Generator trong Python xem nó là gì và tại sao nên dùng Python Generator chúng ta sẽ đi tìm hiểu qua các ví dụ cụ thể.
Generator trong Python là gì?
Hãy cùng thực hiện một ví dụ:
Ví dụ
class FibonacciIterable:
def __init__(self, count=10):
self.a, self.b = 0, 1
self.count = count
def __iter__(self):
i = 0
while True:
if i > self.count:
break
yield self.a
self.a, self.b = self.b, self.a + self.b
i += 1
def test1():
iterable = FibonacciIterable(20)
for f in iterable:
print(f, end=' ')
if __name__ == '__main__':
test1()
Trong ví dụ 1 này chúng ta sử dụng một cách khác để tạo ra chuỗi fibonacci. Hãy để ý ở đây chúng ta chỉ xây dựng một class FibonacciIterable.
Trong hàm tạo, chúng ta gán hai giá trị đầu tiên của chuỗi, a = 0 và b = 1. Biến count chứa số lượng phần tử cần tạo ra.
Điểm mấu chốt của class FibonacciIterable là phương thức __iter__(). Đây là phương thức magic mà bất kỳ class iterable nào đều phải thực thi. Nói theo cách khác, nếu một class thực thi phương thức __iter__(), nó sẽ trở thành một iterable.
Logic của __iter__() không phức tạp:
- Tạo một biến đếm i và một vòng lặp while.
- Nếu biến đếm vượt quá giá trị của count thì phá vỡ vòng lặp.
- Nếu biến đếm trong giới hạn cho phép thì trả lại giá trị của phần tử hiện tại của chuỗi, cập nhật giá trị cho phần tử tiếp theo, và tăng giá trị biến đếm.
Hãy để ý rằng, FibonacciIterable không hề có iterator đi kèm, hoặc cũng có thể nói rằng lớp FibinacciIterable đóng vai trò của của iterable và iterator.
Cấu trúc này đơn giản hơn rất nhiều tổ hợp iterable (thực thi __iter__()) và iterator (thực thi __next__()) mà bạn đã học trong bài trước (xem lại bài học iterable và iterator trong Python).
Python gọi loại cấu trúc sinh ra chuỗi giá trị sử dụng từ khóa yield như vậy là các generator. Bạn có thể thực thi generator trong class hoặc trong hàm. Trong ví dụ 1, FibonacciIterable là một class generator.
Từ khóa yield
Quay lại ví dụ 1, hãy để ý đến điểm đặc thù của __iter__(): từ khóa yield
. Đây là một từ khóa đặt biệt trong Python.
Từ khóa yield trong Python hoạt động tương tự như return ở chỗ nó trả lại giá trị cho hàm gọi, tuy nhiên lại không kết thúc việc thực hiện hàm/phương thức.
Khi gặp yield, Python sẽ trả lại giá trị tương ứng cho hàm gọi và tạm dừng (pause) thực hiện hàm/phương thức. Tạm dừng ở đây mang đúng nghĩa đen: mọi trạng thái của hàm/phương thức vẫn được duy trì và có thể tiếp tục hoạt động trở lại. Khi hàm gọi cần sử dụng đến giá trị tiếp theo, __iter__() sẽ được hồi phục từ trạng thái tạm dừng.
Khi phục hồi từ trạng thái thạm dừng, hàm/phương thức tiếp tục thực hiện từ vị trí nó dừng lại trước đó chứ không bắt đầu lại. Như vậy, cả quá trình thực hiện của hàm/phương thức với vòng lặp và yield tạo ra cả một chuỗi dữ liệu.
Có thể thấy, mô hình hoạt động của yield rất tương đồng với __next__ của iterator.
Đặc điểm này của yield giúp nó có thể sinh ra iterator từ phương thức hoặc hàm mà không cần xây dựng class riêng thực thi phương thức __next__() như trong mô hình iterator thông thường.
yield trong Python hoạt động giống như yield return trong C# hay Java.
Trong ví dụ 1, sử dụng từ khóa yield trong __iter__() biến FibonacciIterable đồng thời thành (1) iterable và (2) iterator. Điều này bạn có thể thấy trong hàm test1(), khi object của FibonacciIterable được sử dụng trực tiếp trong vòng lặp for (với vai trò iterable) trong khi chúng ta không hề xây dựng class iterator nào.
Một đặc điểm quan trọng của yield là thực thi trễ (lazy execution). Thực thi trễ thể hiện rằng chỉ khi nào nơi gọi hàm thực sự cần sử dụng dữ liệu (ví dụ, để in ra hoặc tính toán), hàm tương ứng mới được thực thi.
Bạn có thể sử dụng nhiều yield trong một phàm/phương thức. Hãy xem ví dụ sau:
Ví dụ
class YieldSample:
def __iter__(self):
i = 10
yield i + 10
yield i * 10
yield i / 10
yield i - 10
if __name__ == '__main__':
for value in YieldSample():
print(value)
Ví dụ
20
100
1.0
0
Mỗi lần gặp yield, phương thức sẽ trả lại kết quả tương ứng và tạm dừng chờ khi chương trình cần dữ liệu sẽ chạy tiếp.
Nếu sử dụng yield cùng __iter__() trong class sẽ tạo ra một tổ hợp iterable và iterator mà bạn có thể trực tiếp sử dụng cùng vòng lặp for. Class như vậy được gọi là class generator.
Một đặc điểm quan trọng là bạn cũng có thể sử dụng yield trong hàm để tạo thành hàm generator. Hàm generator thậm chí còn giúp giản lược hơn nữa việc tạo ra các cấu trúc dữ liệu dạng iterable/container.
Hàm generator trong Python
Hãy cùng xem ví dụ sau:
Ví dụ
def fibonacci(count: int = 15):
a, b = 0, 1
i = 0
while True:
if i > count:
break
yield a
a, b = b, a + b
i += 1
def yield_return():
i = 10
yield i + 10
yield i * 10
yield i / 10
yield i - 10
def test1():
iterable = fibonacci(15)
for f in iterable:
print(f, end=' ')
print()
def test2():
for value in yield_return():
print(value)
if __name__ == '__main__':
test1()
test2()
Kết quả thực hiện của ví dụ trên như sau:
Ví dụ
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
20
100
1.0
0
Đây là ví dụ về cách xây dựng hàm generator sử dụng từ khóa yield.
Quy tắc sử dụng và cách hoạt động của yield trong hàm không có gì khác biệt với khi sử dụng trong phương thức của class.
Tuy nhiên, khi sử dụng với hàm, Python sẽ biến hàm đó trở thành một tổ hợp iterable + iterator. Bạn có thể trực tiếp sử dụng hàm này trong vòng lặp for. Hàm này giờ hoạt động giống hệt như class generator cũng như tổ hợp iterable+iterator.
Đây là cách thức nhanh gọn và đơn giản nhất để tạo ra iterator trong Python.
Biểu thức generator
Trong trường hợp bạn cần tạo ra một generator đơn giản để truyền làm tham số cho một hàm khác, bạn có thể sử dụng biểu thức generator.
Biểu thức generator là một biểu thức mà kết quả trả về là một iterator. Bạn cũng có thể hình dung biểu thức generator tương tự như một hàm generator đơn giản không tên và chỉ có thâm hàm.
Hãy xem ví dụ sau:
Ví dụ
squares = (x*x for x in range(1, 20, 2))
for s in squares:
print(s, end=' ')
Trong ví dụ trên, (x*x for x in range(1, 20, 2)) là một biểu thức generator. Kết quả trả về của biểu thức này được biến squares trỏ tới. Biến squares là một iterator. Nói chính xác hơn, squares là một object của class generator.
Để ý rằng, cú pháp của biểu thức generator rất giống với list compression, ngoại trừ cặp dấu ngoặc tròn.
Kết luận
Trong bài học này bạn đã làm quen với generator trong Python với các ý sau:
- Generator là một cú pháp đơn giản hơn giúp bạn tạo ra iterable và iterator thay cho mô hình iterator cơ bản.
- Có thể sử dụng generator với class, function và expression.
- Generator trong Python sử dụng từ khóa yield.