5.1 Iterable và Iterator
Như bạn đã biết, iterators là các đối tượng thực hiện giao thức của iterator, cho phép lần lượt nhận các phần tử từ một tập hợp. Iterators được sử dụng rộng rãi trong Python để duyệt qua các phần tử của các chuỗi như danh sách, tuple và chuỗi.
Xem cách iterator hoạt động và cách sử dụng chúng.
Đối tượng có thể lặp (Iterable)
Để một đối tượng có thể được duyệt qua bằng vòng lặp for, nó phải là một đối tượng có thể lặp – Iterable. Điều này có nghĩa là đối tượng của chúng ta phải thực thi phương thức __iter__(), trả về một đối tượng-iterator.
Đối tượng-iterator (Iterator)
Đây là một đối tượng đặc biệt, có hàm __next__() để trả về phần tử tiếp theo trong chuỗi. Khi hết các phần tử, phương thức __next__() sẽ gọi ra ngoại lệ StopIteration để ngừng việc lặp.
Iterator cũng phải thực thi phương thức __iter__(), trả về chính iterator.
Ví dụ sử dụng các hàm tích hợp của Python
Trong ví dụ này, danh sách numbers là một đối tượng có thể lặp. Chúng ta nhận được iterator bằng cách dùng hàm iter() và sử dụng hàm next() để duyệt qua các phần tử cho đến khi ngoại lệ StopIteration được gọi ra.
# Đối tượng có thể lặp
numbers = [1, 2, 3, 4, 5]
# Nhận iterator từ đối tượng có thể lặp
iterator = iter(numbers)
# Sử dụng iterator để duyệt qua các phần tử
try:
while True:
number = next(iterator)
print(number)
except StopIteration:
pass
Đây là những gì xảy ra khi bạn viết code kiểu:
# Đối tượng có thể lặp
numbers = [1, 2, 3, 4, 5]
for number in numbers:
print(number)
5.2 Bản chất của iterator
Iterator là một đối tượng giúp chúng ta lần lượt duyệt qua một nhóm các phần tử. Cách thực hiện của nó có thể rất khác nhau. Hãy viết một class của mình, trong đó thực hiện tất cả các yêu cầu dành cho iterator.
Bước 1. Bắt đầu tạo class của mình
Để nó lần lượt trả về các số từ start đến end
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
Bước 2. Hỗ trợ hàm __iter__
Bây giờ chúng ta cần thêm hàm __iter__, sẽ trả về đối tượng-iterator, trong đó sẽ gọi hàm __next__(). Chúng ta sẽ trả về tham chiếu đến chính đối tượng của mình - điều này không bị cấm.
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
Bước 3. Hỗ trợ hàm __next__
Bây giờ cần thêm vào đối tượng iterator của chúng ta hàm __next__, hàm sẽ trả về phần tử tiếp theo trong danh sách của chúng ta. Chúng ta chỉ cần sử dụng biến current:
def __next__(self):
current = self.current
self.current += 1
return current
Bước 4. Dừng iterator
Nếu iterator đã trả về hết các giá trị dự kiến, nó phải ném ra ngoại lệ StopIteration. Hãy sửa một chút hàm cuối cùng của chúng ta:
def __next__(self):
if self.current >= self.end: raise StopIteration
current = self.current
self.current += 1
return current
Tuyệt. Bây giờ có thể sử dụng iterator của chúng ta. Đây là ví dụ của toàn bộ mã của chúng ta:
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
# Tạo một instance của iterator tùy chỉnh
my_iter = MyIterator(1, 5)
# Sử dụng iterator để duyệt qua các phần tử
for num in my_iter:
print(num)
5.3 Iterator chính xác
Iterator từ ví dụ trước không tốt ở đâu? Đúng, nó là một iterator, nó hoạt động, nhưng nó quá đơn giản. Không thể đồng thời duyệt qua một tập hợp các phần tử bằng nhiều iterators khác nhau.
Đúng hơn là viết mã trả về không phải tham chiếu đến chính nó trong phương thức __iter__, mà là một đối tượng riêng biệt, sẽ trả về chính xác tất cả các phần tử.
Ví dụ:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
item = self.data[self.index]
self.index += 1
return item
# Sử dụng
my_iterable = MyIterable([1, 2, 3, 4])
for item in my_iterable:
print(item)
Trong ví dụ này, chúng ta có hai lớp — lớp đầu tiên truyền một tập hợp, mà chúng ta sẽ duyệt qua iterator. Còn lớp thứ hai — chính là iterator, trả về các phần tử của tập hợp trong phương thức next(). Nó cũng khá đơn giản, nhưng đây là cách đúng để thêm iterator vào trong các lớp của bạn.
GO TO FULL VERSION