Iterator là các đối tượng cho phép ta lấy từng phần tử của nó, hành động này có thể được lặp đi lặp lại. Trong bài viết này, chúng ta sẽ cùng bạn sẽ tìm hiểu cách iterator hoạt động trong Python và cách bạn có thể xây dựng trình lặp của riêng mình bằng các phương thức __iter__ và __next__.
Iterator trong Python là gì?
Iterator ở khắp mọi nơi trong Python, bên trong các vòng lặp, comprehension, generator…
Nó đơn giản chỉ là các đối tượng cho phép ta lấy từng phần tử, bất cứ khi nào bạn sử dụng vòng lặp hay các kĩ thuật để có được giá trị một nhóm phần tử ở một thời điểm nào đó.
Về mặt kỹ thuật, Iterator trong Python phải thực hiện hai phương thức đặc biệt là __iter__() và __next__(), gọi chung là giao thức iterator (Iterator Protocol)
- Phương thức __iter__ trả về chính đối tượng iterator. Phương thức này được yêu cầu cài đặt cho cả đối tượng “iterable” và iterator để có thể sử dụng các câu lệnh for và in.
- Phương thức __next__ trả về phần tử tiếp theo. Nếu không còn phần tử nào nữa thì sẽ có lỗi StopIteration xảy ra.
Iterable object là một đối tượng sau khi sử dụng các phương thức sẽ trả về một iterator, ví dụ như Chuỗi, List, Tuple.
Iter() là một hàm dựng sẵn trong Python nhận đầu vào là một đối tượng iterable và trả về kết quả là một iterator.
# Khai bao mot list
my_list = [4, 7, 0, 3]
# lay mot iterator bang cach su dung iter()
my_iter = iter(my_list)
## su dung next()
#prints 4
print(next(my_iter))
#prints 7
print(next(my_iter))
## next(obj) chinh la obj.__next__()
#prints 0
print(my_iter.__next__())
#prints 3
print(my_iter.__next__())
## Xay ra loi StopIteration vi het gia tri tra ve
next(my_iter)
Chạy chương trình, kết quả trả về là:
4
7
0
3
Traceback (most recent call last):
File "<stdin>", line 24, in <module>
next(my_iter)
StopIteration
Một cách tương tự trả về kết quả này là ta sử dụng vòng lặp for.
>>> for element in my_list:
... print(element)
...
4
7
0
3
Cách vòng lặp hoạt động
Như chúng ta thấy trong ví dụ trên, vòng lặp for có thể lặp lại tự động thông qua việc sử dụng danh sách.
Trong thực tế, vòng lặp for có thể lặp lại trên bất kỳ iterable nào. Chúng ta hãy xem kỹ hơn về cách vòng lặp for được thực hiện trong Python.
for element in iterable:
# do something with element
# iter_obj là một iterator object tạo từ iterable
iter_obj = iter(iterable)
# vòng lặp
while True:
try:
# sử dụng next
element = next(iter_obj)
except StopIteration:
# nếu xảy ra lỗi StopIteration thì vòng lặp sẽ được break ra ngoài
break
Ở ví dụ này, bên trong vòng lặp for ta tạo một iterator object tên là iter_obj bằng cách gọi iter() trên iterable.
Và như bạn thấy thì vòng lặp for ở đây chính là vòng lặp while vô hạn. Next() bên trong vòng lặp lấy ra các phần tử để thực hiện các câu lệnh ở for loop. Đến khi lấy hết các giá trị thì ngoại lệ StopIteration sẽ được sinh ra và vòng lặp kết thúc.
Xây dựng trình vòng lặp Iterator trong Python
Chúng ta có thể tự xây dựng iterator là một class. Xây dựng Iterator rất dễ dàng trong Python, chúng ta chỉ cần thực hiện các phương thức __iter__() và __next__().
class PowTwo:
def __init__(self, max = 0):
self.max = max
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n <= self.max:
result = 2 ** self.n
self.n += 1
return result
else:
raise StopIteration
Phương thức __iter__ sẽ làm đối tượng trở thành đối tượng iterable.
Giá trị trả về của __iter__ là một iterator. Nó cần có phương thức __next__ và trả về StopIteration nếu không còn phần thử nào nữa.
Tạo ra một iterator và chạy chương trình như sau:
>>> a = PowTwo(4)
>>> i = iter(a)
>>> next(i)
1
>>> next(i)
2
>>> next(i)
4
>>> next(i)
8
>>> next(i)
16
>>> next(i)
Traceback (most recent call last):
...
StopIteration
Chúng ta cũng có thể sử dụng một vòng lặp for để lặp iterator
>>> for i in PowTwo(5):
... print(i)
...
1
2
4
8
16
32
Iterator lặp vô hạn trong Python
Không phải tất cả các đối tượng iterator đều sẽ được gọi hết các phần tử và kết thúc khi không còn phần tử. Có một số trường hợp iterator sẽ lặp vô hạn. Ví dụ như sau:
>>> int()
0
>>> inf = iter(int,1)
>>> next(inf)
0
>>> next(inf)
0
Chúng ta có thể thấy rằng hàm int() luôn trả về 0. Vì vậy, việc truyền nó dưới dạng iter(int, 1) sẽ trả về một iterator lặp cho đến khi giá trị trả về bằng 1. Điều này không bao giờ xảy ra và đây chính là iterator lặp vô hạn. Bạn cần chú ý khi xử lý trong các trường hợp thế này.
Ngoài ra, bạn cũng có thể tự xây dựng iterator lặp vô hạn. Ví dụ sau đây sẽ lặp vô hạn và trả về các số lẻ vì không có điều kiện dừng nào cả.
class InfIter:
def __iter__(self):
self.num = 1
return self
def __next__(self):
num = self.num
self.num += 2
return num
>>> a = iter(InfIter())
>>> next(a)
1
>>> next(a)
3
>>> next(a)
5
>>> next(a)
7
Ưu điểm của việc sử dụng Iterator lặp là chúng tiết kiệm tài nguyên. Giống như hiển thị ở trên, bạn có thể nhận được tất cả các số lẻ mà không lưu trữ trên toàn bộ hệ thống số ở bộ nhớ.