Trong phần này chúng ta sẽ tìm hiểu về Iterator và Generator.
Iterator là một khái niệm chỉ các lớp dạng danh sách cho phép chúng ta duyệt qua chúng mà không cần quan tâm đến kiểu dữ liệu đó là gì.
Để một lớp được gọi là Iterator thì lớp đó phải hỗ trợ 2 phương thức là __iter__()
và __next__()
. Cả 2 phương thức đó được gọi chung là giao thức iterator (Iterator Protocol). Trong đó phương thức __iter__()
chỉ đơn giản là trả về đối tượng tham chiếu tới chính nó, còn phương thức __next__()
trả về phần tử tiếp theo có trong danh sách cho đến phần tử cuối cùng thì giải phóng exception StopIteration
.
Hầu hết các kiểu dữ liệu dạng danh sách trong Python như kiểu list, tuple, string, dictionary đều hỗ trợ giao thức Iterator.
str = "formidable" it = iter(str) print (it.__next__()) print (it.__next__()) print (it.__next__()) print (list(it))
Trong đoạn code trên, chúng ta sử dụng tính tăng iterator của một string. Hàm iter()
sẽ gọi đến phương thức __iter__()
có sẵn trong lớp string để lấy về đối tượng tham chiếu đến chính nó. Phương thức __next__()
sẽ trả về phần tử tiếp theo trong danh sách. Ngoài ra bạn còn có thể dùng các hàm như list(), tuple()
để tạo các đối tượng danh sách mới dựa trên một iterator.
f o r ['m', 'i', 'd', 'a', 'b', 'l', 'e']
Trong ví dụ dưới đây, chúng ta sẽ viết một lớp riêng có hỗ trợ giao thức Iterator.
class seq: def __init__(self): self.x = 0 def __next__(self): self.x += 1 return self.x**self.x def __iter__(self): return self s = seq() n = 0 for i in s: print (i) n += 1 if n > 10: break
Trong đoạn code trên, chúng ta tạo một dãy số dài vô tận. Khi chúng ta sử dụng vòng lặp for, vòng lặp sẽ tự động gọi hàm iter()
lên đối tượng của chúng ta để lấy về một đối tượng tham chiếu tới chính nó, và cứ mỗi lần lặp nó lại gọi đến phương thức __next__()
đế lấy phần tử tiếp theo trong dãy.
def __next__(self): self.x += 1 return self.x**self.x
Phương thức __next__()
trả về phần tử tiếp theo trong dãy.
def __iter__(self): return self
Phương thức __iter__
trả về đối tượng tham chiếu đến chính nó (hay còn gọi là đối tượng iterator).
if n > 10: break
Do chúng ta tạo một danh sách dài vô tận nên phải có câu lệnh để ngắt vòng lặp.
1 4 27 256 3125 46656 823543 16777216 387420489 10000000000 285311670611
Trong ví dụ dưới đây, chúng ta sẽ ngắt vòng lặp bằng cách giải phóng exception StopIteration
.
class seq14: def __init__(self): self.x = 0 def __next__(self): self.x += 1 if self.x > 14: raise StopIteration return self.x**self.x def __iter__(self): return self s = seq14() for i in s: print (i)
Trong đoạn code trên chúng ta in ra 14 số đầu tiên trong dãy.
if self.x > 14: raise StopIteration
StopIteration
sẽ ngắt vòng lặp.
Generator
Generator chẳng qua cũng chỉ là Iterator, chúng cũng tạo ra một đối tượng kiểu danh sách, nhưng bạn chỉ có thể duyệt qua các phần tử của generator một lần duy nhất vì generator không lưu dữ liệu trong bộ nhớ mà cứ mỗi lần lặp thì chúng sẽ tạo phần tử tiếp theo trong dãy và trả về phần tử đó.
Đoạn code dưới đây ví dụ cách định nghĩa một generator.
def gen(): x, y = 1, 2 yield x, y x += 1 yield x, y it = gen() print (it.__next__()) print (it.__next__()) try: print (it.__next__()) except StopIteration: print ("Iteration finished")
Để định nghĩa một generator thì chúng ta dùng từ khóa def
giống như khi định nghĩa một hàm. Trong generator chúng ta dùng từ khóa yield
để trả về các phần tử.
Từ khóa yield
có chức năng y hệt như từ khóa return,
chỉ khác ở chỗ là khi bạn gọi phương thức __next__()
lần thứ nhất thì generator thực hiện các công việc tính toán giá trị rồi gặp từ khóa yield
nào thì nó sẽ trả về các phần tử tại ví trí đó, khi bạn gọi phương thức __next__()
lần thứ hai thì generator không bắt đầu chạy tại vị trí đầu tiên mà bắt đầu ngay phía sau từ khóa yield
thứ nhất. Cứ như thế generator tạo ra các phần tử trong dãy, cho đến khi không còn gặp từ khóa yield
nào nữa thì giải phóng exception StopIteration
.
(1, 2) (2, 2) Iteration finished