Cách Tạo Iterator Python: Hướng Dẫn
Iterator có khắp nơi trong Python; thật khó để học ngay cả cơ bản mà không nghe từ "iterator" hay "iterable" (có thể lặp).
Iterator là đối tượng có thể lặp qua. Mặc dù có một số đối tượng tích hợp sẵn bạn có thể lặp qua, bạn cũng có thể tự định nghĩa.
Trong hướng dẫn này, chúng ta sẽ nói về iterator Python, cách chúng hoạt động, và cách dùng chúng trong mã. Chúng ta sẽ xây dựng iterator riêng để bạn học các tính năng cốt lõi của chúng trong hành động. Mã trong hướng dẫn này tương thích với cài đặt Python 3.x.
Hãy bắt đầu!
Iterator Là Gì?
Iterator là đối tượng bạn có thể lặp qua. Iterator sẽ trả về dữ liệu từ đối tượng đó, từng mục một.
Bạn có thể kiểm tra đối tượng có lặp được không bằng cách thử chạy nó qua vòng lặp for; nếu bạn có thể lặp qua đối tượng, nghĩa là nó là iterable. Chuỗi, danh sách, bộ, và dictionary là ví dụ kiểu dữ liệu tích hợp là đối tượng iterable.
Đối tượng iterator trong Python phải có hai phương thức:
__iter__(): Cho phép lấy iterator.__next__(): Cho phép lấy giá trị tiếp theo trong iterator.
Dấu gạch dưới __ dùng trong iterator vì chúng ký hiệu hàm đặc biệt. Để định nghĩa iterator, bạn phải dùng hai hàm đặc biệt này. Cùng nhau, chúng gọi là giao thức iterator (iterator protocol). Các phương thức này thường phát âm là "iter và next methods".
Cách Xây Dựng Iterator Vô Hạn
Chúng ta bắt đầu với cơ bản: xây dựng iterator vô hạn. Đây là kiểu iterator đơn giản nhất. Nó đơn giản đến mức sẽ chạy mãi mãi. Chúng ta sẽ sửa điều này sau trong hướng dẫn.
Hãy bắt đầu bằng định nghĩa lớp gọi là Cakes, sẽ là lớp iterator của chúng ta:
class Cakes:
def __init__(self, value):
self.value = value
Chúng ta đã khai báo phương thức __init__ sẽ thực thi ngay khi đối tượng lớp được khởi tạo. Phương thức __init__ gọi là constructor (hàm khởi tạo). Trong ví dụ này, __init__ nhận một giá trị: value.
Tiếp theo, chúng ta tạo giao thức iterator:
def __iter__(self):
return self
def __next__(self):
return self.value
Phương thức __iter__ dùng để lấy đối tượng iterator. Phương thức __next__ cho phép lấy giá trị tiếp theo trong đối tượng lặp được chúng ta chỉ định.
Hãy kiểm tra mã hoạt động cho đến nay. Chúng ta làm bằng cách định nghĩa đối tượng lớp như sau:
cake = Cakes(["Vanilla Sponge"])
Cho đến nay tốt! Bạn có thể tự hỏi: tại sao mã này không làm gì? Đó là vì lớp chúng ta chỉ trả về hàm iterator.
Chúng ta phải lặp qua iterator để kiểm tra hoạt động. Chúng ta làm bằng vòng lặp for-in:
for c in cake:
print(c)
Mã trả về:
Vanilla Sponge Vanilla Sponge …
Sự nguyền rủa của Vanilla Sponge! Nó cứ in ra bảng điều khiển. Điều gì xảy ra? Chúng ta vừa tạo iterator đầu tiên. Nó rất đơn giản, nhưng hoạt động.
Kiểm Tra Iterator Dùng Vòng Lặp for-in Trong Python
Trước khi làm iterator hữu ích, hãy ôn lại khái niệm vòng lặp for-in. Trong ví dụ trước, chúng ta viết vòng lặp for-in để kiểm tra mã:
for c in cake:
print(c)
Cách vòng lặp for-in hoạt động là: nó trước tạo iterator của đối tượng dùng phương thức __iter__(). Vòng lặp for-in sau đó gọi __next__() cho đến khi không có giá trị trả về.
Bạn có thể nghĩ vòng lặp for-in như vòng lặp while đơn giản. Trong khi có giá trị iterator có thể trả về, vòng lặp for-in sẽ trả về những giá trị đó.
Tạo Iterator Hữu Ích
Iterator trước không hữu ích lắm. Nó chỉ in cùng giá trị lại và lại. Với đó, nó vẫn là iterator: có khả năng đọc tất cả đối tượng trong iterable.
Chúng ta sẽ tạo iterator lặp qua mảng Python. Mảng này chứa danh sách bánh.
Để dừng iterator lặp vô hạn, chúng ta sẽ theo dõi số giá trị bên trong iterable, và số lần iterator đã thực thi. Điều này cho phép dừng iterator khi đã lặp qua tất cả mục trong iterable.
Mở file Python mới và dán mã sau:
class Cakes:
def __init__(self, value):
self.value = value
self.max = len(value)
self.count = 0
def __iter__(self):
return self
def __next__(self):
if self.count < self.max:
to_return = self.value[self.count]
self.count += 1
return to_return
else:
raise StopIteration
Iterator này phức tạp hơn iterator trước. Đó là lý do chúng ta xây dựng iterator vô hạn trước. Trong iterator này, chúng ta khởi tạo ba giá trị:
- value: Mục chúng ta muốn lặp qua.
- max: Số mục trong iterable của chúng ta.
- count: Số lần iterator đã thực thi.
Trong phương thức __next__(), chúng ta thêm câu lệnh "if". Câu lệnh này kiểm tra số lần iterator chạy (count) có nhỏ hơn số mục iterable nên lặp (max) không.
Nếu "count" nhỏ hơn "max", iterator sẽ tìm giá trị tiếp theo trong đối tượng iterable. Sau đó cộng 1 vào biến "count" để theo dõi số đối tượng còn lại để lặp. Sau đó, trả về giá trị chúng ta tính toán.
Lưu ý quan trọng: chúng ta gán giá trị cho biến "to_return" trước khi tăng biến "count". Điều này đảm bảo chúng ta truy cập giá trị ở lần lặp hiện tại của iterator.
Nếu đã lặp qua mọi mục trong danh sách, ngoại lệ StopIteration sẽ được raise. Điều này dừng chương trình khi đã lặp qua mọi mục.
Hãy xem iterator hoạt động. Thêm mã sau vào chương trình chính và thực thi:
cake = Cakes(["Vanilla Sponge"])
for c in cake:
print(c)
Mã trả về:
Vanilla Sponge
Hãy thêm vài mục vào danh sách xem sao:
cake = Cakes(["Vanilla Sponge", "Carrot", "Coffee"])
for c in cake:
print(c)
Mã trả về:
Vanilla Sponge Carrot Coffee
Chúng ta đã làm được! Chúng ta tạo thành công iterator lặp qua danh sách bánh. Bạn xứng đáng một cái bánh – hoặc ít nhất một cupcake – vì đã đọc đến đây.
Kết Luận
Iterator cho phép tạo đối tượng có thể lặp qua. Iterator có thể lặp qua danh sách, chuỗi, hoặc đối tượng iterable khác, và thực hiện hành động cụ thể.
Bạn sẵn sàng thử thách? Tạo iterator lặp qua danh sách số và trả về mỗi số nhân đôi.
Mã cho iterator này sẽ tương tự ví dụ trên – vẫn dùng giao thức iterator – nhưng nên nhân mỗi số trong danh sách gấp đôi trước khi trả về chương trình chính.
Bây giờ bạn đã sẵn sàng viết iterator Python riêng như chuyên gia!