Bài 3 · Nâng cao · 24 phút· Cập nhật 11/06/2026
Iterator & generator
Biên soạn bởi Nguyễn Anh Tuấn
Hiểu bản chất vòng for trong Python: giao thức iterator (__iter__/__next__), generator & yield, lazy evaluation và đường ống itertools.
Vòng for trông đơn giản, nhưng bên dưới là một giao thức: Python gọi iter() để lấy một iterator, rồi gọi next() liên tục tới khi gặp StopIteration. Bấm qua từng bước:
- ▸for x in ds ≡ it = iter(ds); rồi lặp x = next(it) tới khi StopIteration.
- ▸Bạn không thấy StopIteration vì for tự bắt nó để dừng.
- ▸Hiểu giao thức này = bạn TỰ tạo được thứ “for được”.
Iterable = lặp được (có __iter__); iterator = giữ vị trí (có __next__):
iterator.py
class DemNguoc:
def __init__(self, n):
self.n = n
def __iter__(self): # tra ve mot iterator (chinh no)
return self
def __next__(self): # tra phan tu ke tiep
if self.n <= 0:
raise StopIteration # het -> bao dung
self.n -= 1
return self.n + 1
for x in DemNguoc(3):
print(x) Kết quả khi chạy
3 2 1
- ▸__iter__ trả về iterator; __next__ trả phần tử kế, hết thì raise StopIteration.
- ▸Một iterator dùng XONG là cạn - phải tạo mới để lặp lại.
- ▸Cách này dài dòng → generator (Bước 3) làm gọn hơn nhiều.
Hàm có yield là một generator. Gọi nó KHÔNG chạy thân hàm; mỗi next() chạy tới yield kế rồi tạm dừng, giữ nguyên trạng thái:
generator.py - tương đương DemNguoc, gọn hơn nhiều
def dem_nguoc(n):
while n > 0:
yield n # tra ra n roi TAM DUNG tai day
n -= 1
for x in dem_nguoc(3):
print(x) Kết quả khi chạy
3 2 1
- ▸Gọi dem_nguoc(3) chỉ TẠO generator - chưa chạy gì (lười).
- ▸yield = trả một giá trị rồi tạm dừng; next() sau chạy tiếp từ chỗ đó.
- ▸Generator tự lo __iter__/__next__/StopIteration cho bạn.
Vì generator chỉ sinh từng phần tử khi cần, nó xử lý được dữ liệu lớn hay vô hạn mà không dựng tất cả trong RAM:
lazy.py - generator expression (không ngoặc vuông)
# List comprehension: DUNG ca 1 trieu phan tu trong bo nho
tong1 = sum([x * x for x in range(1_000_000)])
# Generator expression: LUOI, chi giu mot phan tu tai mot thoi diem
tong2 = sum(x * x for x in range(1_000_000))
print(tong1 == tong2) Kết quả khi chạy
True
- ▸[ ... ] = list (eager, tốn bộ nhớ); ( ... ) = generator (lazy).
- ▸Đưa thẳng vào hàm: sum(x*x for x in ds) - khỏi ngoặc trong, không dựng list trung gian.
- ▸Hợp đọc file lớn, luồng dữ liệu, dãy vô hạn - giữ ít trong RAM.
Generator dùng một lần
Generator không chỉ cho ra giá trị - nó còn nhận vào. send(v) chạy tiếp và biến v thành kết quả của yield đang dừng (đây là nền của coroutine):
send.py - generator tính trung bình động
import inspect
def averager():
total = 0; n = 0; avg = None
while True:
x = yield avg # GUI x vao day; tra avg ra ngoai
total += x; n += 1
avg = total / n
g = averager()
print(inspect.getgeneratorstate(g)) # GEN_CREATED
next(g) # "moi" toi yield dau tien
print(g.send(10)) # 10.0
print(g.send(20)) # 15.0 ((10+20)/2)
g.close()
print(inspect.getgeneratorstate(g)) # GEN_CLOSED Kết quả khi chạy
GEN_CREATED 10.0 15.0 GEN_CLOSED
yield_from.py - ghép nhiều generator
def chain(*its):
for it in its:
yield from it # chuyen tiep moi gia tri cua it ra ngoai
print(list(chain([1, 2], [3, 4]))) Kết quả khi chạy
[1, 2, 3, 4]
- ▸send(v): vừa lấy ra vừa GỬI v vào (v = kết quả của yield). Phải next() “mồi” trước.
- ▸close() dừng generator (trạng thái GEN_CLOSED); inspect.getgeneratorstate xem trạng thái.
- ▸yield from delegate sang generator/iterable con - gọn & chuyển tiếp cả send.
Generator là tiền thân của async
Nhiều generator nối nhau thành một đường ống xử lý lười; module itertools cho sẵn nhiều mảnh ghép:
pipeline.py
import itertools
def doc_so(ds):
for s in ds:
yield int(s)
def chi_chan(nums):
for n in nums:
if n % 2 == 0:
yield n
duong_ong = chi_chan(doc_so(["1", "2", "3", "4", "5", "6"]))
print(list(itertools.islice(duong_ong, 2))) # chi lay 2 phan tu dau Kết quả khi chạy
[2, 4]
- ▸Mỗi tầng generator chỉ lấy phần tử khi tầng sau yêu cầu - lười suốt đường ống.
- ▸itertools: count (đếm vô hạn), islice (cắt), chain (nối), takewhile/groupby… đều trả iterator.
- ▸Đường ống generator = xử lý dữ liệu lớn theo dòng, ít bộ nhớ.
Tiếp theo
Câu hỏi thường gặp
ITERABLE là thứ LẶP ĐƯỢC (list, tuple, str, dict, file…): nó có __iter__ trả về một iterator. ITERATOR là thứ GIỮ VỊ TRÍ: nó có __next__ trả phần tử kế tiếp, hết thì ném StopIteration. Vòng for gọi iter(iterable) một lần để lấy iterator, rồi gọi next() liên tục.
for x in ds: ... tương đương: it = iter(ds); rồi lặp x = next(it) cho tới khi next() ném StopIteration thì dừng. Bạn không bao giờ thấy StopIteration vì for tự bắt nó.
Hàm có yield là một GENERATOR. Gọi nó KHÔNG chạy thân hàm - chỉ trả về một generator object. Mỗi lần next(), thân chạy tới yield kế tiếp rồi TẠM DỪNG (giữ nguyên biến cục bộ); lần next() sau chạy tiếp từ chỗ dừng.
next(g) chạy tiếp và LẤY giá trị yield ra. send(v) làm điều đó NHƯNG còn GỬI v VÀO: v trở thành KẾT QUẢ của biểu thức yield đang tạm dừng (x = yield ...). Nhờ vậy generator thành hai chiều - vừa cho ra, vừa nhận vào (nền của coroutine). Phải next(g) một lần để "mồi" tới yield đầu trước khi send.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Vòng for x in ds tương đương với chuỗi thao tác nào ở bên dưới?
- 1
Bản chất vòng for
Viết lại
for x in [10,20,30]bằngiter()/next()thủ công, bắtStopIterationđể dừng.Hoàn thành khi: Dùng
it = iter(...)vàwhile True: next(...)trongtry/except StopIteration- in 10, 20, 30 rồi dừng. - 2
Iterator rồi generator
Viết
DemNguoc(n)hai cách: (a) class với__iter__/__next__; (b) generator vớiyield. So số dòng.Hoàn thành khi: Cả hai cho 3,2,1; bản generator ngắn hơn hẳn và tự lo
__iter__/__next__/StopIteration. - 3
Lười tiết kiệm bộ nhớ
So
sum([x*x for x in range(1_000_000)])vớisum(x*x for x in range(1_000_000)). Khác gì về bộ nhớ?Hoàn thành khi: Bản generator (không ngoặc vuông) không dựng list 1 triệu phần tử → tốn ít bộ nhớ hơn nhiều, kết quả như nhau.
- 4
Generator nhận giá trị
Viết generator
averager():x = yield avg; cập nhật trung bình. Mồi bằngnext(), rồisend(10),send(20).Hoàn thành khi:
send(10)→10.0;send(20)→15.0. Bạn giải thích vì sao phảinext()“mồi” trước khisend. - 5
yield from
Viết
chain(*its)dùngyield fromđể nối nhiều iterable. Kiểmlist(chain([1,2],[3,4])).Hoàn thành khi:
[1, 2, 3, 4]; bạn thấyyield fromgọn hơn vòngforlồng.