f(), chưa chạy.- Phân biệt Iterable (có
__iter__) và Iterator (có__next__) — vì sao đây là 2 khái niệm khác nhau. - Đọc được vòng
fornhư "đường tốc ký" cho cặpiter()+next(). - Tự build iterable bằng class với
__iter__/__next__. - Viết generator function bằng
yield; viết generator expression bằng dấu(). - Hiểu lazy vs eager và khi nào nên chọn cái nào.
- Tạo sequence vô hạn mà không treo máy — kết hợp với
islice/break. - Dùng
yield fromđể delegate, hiểu vì sao nó hơn vòngfor ... yield. - Biết các tool kinh điển trong
itertools:chain,count,cycle,repeat,islice,groupby,combinations,permutations,product,accumulate. - Nắm khái niệm
send()/throw()/close()như nền cho coroutine cũ.
1. Iterable vs Iterator — hai khái niệm tách bạch
Đây là chìa khoá để hiểu mọi thứ về sau. Python tách bạch 2 khái niệm:
- Iterable: đối tượng có thể bắt đầu lặp. Có method
__iter__()trả về một iterator. Ví dụ:list,tuple,str,dict,set,range, file object. - Iterator: đối tượng đang trong tiến trình lặp. Có method
__next__()trả về phần tử kế tiếp hoặc raiseStopIterationkhi hết.
__iter__()VD: list, str, dict, range
__next__()Trả value hoặc raise
StopIteration
Mọi iterator cũng là iterable (__iter__ của iterator trả về chính nó). Nhưng iterable
chưa chắc đã là iterator — list không có __next__, bạn phải gọi iter()
để có iterator của nó.
Iterable = quyển sách. Có thể "bắt đầu đọc" nhiều lần, từ trang 1.
Iterator = cái thư mục đang kẹp trang. Mỗi lần "next" lật một trang. Đọc hết thì
báo "hết" (StopIteration). Một thư mục chỉ đi được một chiều, không tua lại.
2. Built-in iter() và next()
Vòng for x in xs thực ra là cú pháp đường cho đoạn này:
# Python viết:
for x in [10, 20, 30]:
print(x)
# Ngầm tương đương:
_it = iter([10, 20, 30]) # lấy iterator
while True:
try:
x = next(_it) # lấy giá trị kế tiếp
except StopIteration:
break # hết → thoát vòng
print(x)
iter(obj) gọi obj.__iter__(). next(it) gọi it.__next__().
Cả hai có thể tự gọi tay khi cần điều khiển tinh vi.
it = iter([1, 2, 3])
next(it) # 1
next(it) # 2
next(it) # 3
next(it) # StopIteration
# next có default — tránh exception
it = iter([1])
next(it, 'hết') # 1
next(it, 'hết') # 'hết' (không raise)
it = iter([1, 2, 3])
list(it) # [1, 2, 3] ← consume hết
list(it) # [] ← rỗng! iterator đã "cạn"
# Nhưng iterable lấy lại được iterator mới:
xs = [1, 2, 3]
list(iter(xs)) # [1, 2, 3]
list(iter(xs)) # [1, 2, 3] ← mỗi lần iter() tạo iterator MỚI
Bug điển hình: lưu generator (là iterator) vào biến, rồi for nó hai lần — lần hai sẽ "không có gì".
Nếu cần lặp nhiều lần, hãy ép sang list/tuple hoặc viết hàm trả generator mới mỗi lần gọi.
3. Tự build iterable bằng class
Có 2 cách thiết kế:
- Class chính nó là iterator: implement cả
__iter__(trảself) lẫn__next__. Đơn giản, nhưng không lặp lại được. - Tách iterable và iterator: class chính là iterable,
__iter__trả về một object iterator riêng. Hỗ trợ lặp đồng thời / nhiều lần.
class Countdown:
"""Đếm ngược từ start về 0."""
def __init__(self, start: int):
self.current = start
def __iter__(self):
return self # chính nó là iterator
def __next__(self):
if self.current < 0:
raise StopIteration
v = self.current
self.current -= 1
return v
# Sử dụng
for n in Countdown(3):
print(n) # 3, 2, 1, 0
# Gotcha: chạy lại không reset
c = Countdown(3)
list(c) # [3, 2, 1, 0]
list(c) # [] ← state đã hết
class Range:
def __init__(self, stop: int):
self.stop = stop
def __iter__(self):
return _RangeIter(self.stop) # iterator MỚI mỗi lần
class _RangeIter:
def __init__(self, stop):
self.i = 0
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.i >= self.stop:
raise StopIteration
v = self.i
self.i += 1
return v
r = Range(3)
list(r) # [0, 1, 2]
list(r) # [0, 1, 2] ← lặp lại OK
Nếu object đại diện collection (như list, range, queryset) — chọn cách 2 để lặp nhiều lần được.
Nếu object đại diện stream / cursor (như iterator file, network) — cách 1 đủ.
Trong 95% trường hợp tự build, bạn nên dùng generator function (mục 4 dưới) thay vì viết tay 2 class — Python sẽ tự lo state machine.
4. Generator function — yield
Generator function là hàm có chứa yield. Khi gọi, nó không chạy ngay
mà trả về một generator object (vừa là iterable vừa là iterator). Mỗi next()
chạy thân hàm đến yield tiếp theo, trả value, rồi pause giữ nguyên state local.
def count_up_to(n: int):
for i in range(n):
yield i
g = count_up_to(3) # KHÔNG chạy — chỉ tạo generator object
print(type(g)) # <class 'generator'>
next(g) # 0 (chạy đến yield đầu, pause)
next(g) # 1
next(g) # 2
next(g) # StopIteration
# Hoặc consume bằng for / list
list(count_up_to(5)) # [0, 1, 2, 3, 4]
for i in count_up_to(3):
print(i) # 0, 1, 2
yield. State được giữ.return hoặc raise. Mọi next() đều StopIteration.
Hàm thường: gọi → chạy đến hết → return.
Generator function: gọi → không chạy gì → trả "remote control". Mỗi lần bấm next(),
chạy đến nút "Pause" tiếp theo (yield), giao giá trị, rồi dừng — biến local vẫn còn nguyên.
Khi return / hết thân hàm → raise StopIteration tự động.
Có thể có nhiều yield theo dòng tuyến tính:
def vai_buoc():
print('A')
yield 1
print('B')
yield 2
print('C')
g = vai_buoc()
next(g) # in 'A', trả 1
next(g) # in 'B', trả 2
next(g) # in 'C', rồi StopIteration
5. Generator expression — (x for x in ...)
Cùng cú pháp với list comprehension, nhưng đổi [] thành ():
# List comprehension — EAGER, tạo cả list ngay
squares_list = [x**2 for x in range(10)]
type(squares_list) # <class 'list'>
len(squares_list) # 10
# Generator expression — LAZY, không compute trước
squares_gen = (x**2 for x in range(10))
type(squares_gen) # <class 'generator'>
len(squares_gen) # TypeError — generator không có len
# Khi truyền vào hàm nhận iterable, có thể bỏ () thừa:
sum(x**2 for x in range(10)) # 285 — đẹp, gọn
max(len(w) for w in words)
Khi xử lý file 10GB, log stream, hay sequence vô hạn — luôn dùng generator expression để tránh materialize toàn bộ vào RAM. Nếu data nhỏ và cần lặp nhiều lần — list comprehension lại tốt hơn (cache result, không cần regenerate).
6. Lazy vs eager — đánh đổi RAM và CPU
| — | List comprehension (eager) | Generator (lazy) |
|---|---|---|
| Khi nào compute | Ngay khi tạo | Khi next() |
| Bộ nhớ | O(n) — giữ cả collection | O(1) — chỉ giữ state hiện tại |
| Lặp lại | Bao nhiêu lần cũng được | Một lần, hết là cạn |
| Random access | lst[5] | Không — phải next() tuần tự |
Có len() | Có | Không |
| Vô hạn? | ❌ — sẽ hết RAM | ✅ |
| Sequence rất lớn / file | Tránh | Idiomatic |
import sys
# 10 triệu int
lst = [i for i in range(10_000_000)]
gen = (i for i in range(10_000_000))
sys.getsizeof(lst) # ~80,000,056 bytes (~80MB)
sys.getsizeof(gen) # 200 bytes ← chênh 400,000 lần
len, không indexablegen = (x for x in range(10))
len(gen) # TypeError: object of type 'generator' has no len()
gen[3] # TypeError: 'generator' object is not subscriptable
# Nếu cần — ép sang list, nhưng nhớ đánh đổi RAM:
len(list(gen)) # 10 nhưng gen đã consume xong, lần sau sẽ là 0
7. Generator vô hạn
Vì generator lazy, ta có thể viết sequence vô hạn mà vẫn an toàn — miễn người consume biết dừng.
def count():
i = 0
while True:
yield i
i += 1
# Cách 1: break khi đủ
for x in count():
if x > 5:
break
print(x) # 0, 1, 2, 3, 4, 5
# Cách 2: itertools.islice (slicing iterator)
from itertools import islice
list(islice(count(), 6)) # [0, 1, 2, 3, 4, 5]
list() generator vô hạn
list(count()) sẽ chạy mãi và treo máy. Luôn islice hoặc kiểm tra điều kiện
break trước khi materialize.
Ví dụ Fibonacci infinite:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
list(islice(fibonacci(), 10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
8. yield from — delegate generator
Khi một generator muốn "phát lại" toàn bộ giá trị của generator (hay iterable) khác, viết for x in g: yield x
dài dòng. Python 3.3+ có cú pháp gọn hơn:
# Cách dài
def chain_old(*iterables):
for it in iterables:
for x in it:
yield x
# Cách gọn — yield from
def chain(*iterables):
for it in iterables:
yield from it
list(chain([1, 2], (3, 4), range(5, 7)))
# [1, 2, 3, 4, 5, 6]
Ngoài cú pháp gọn, yield from còn forward được send(),
throw(), và giá trị return của sub-generator — đây là điều phiên bản for ... yield x không làm được. Sẽ rõ hơn ở mục 9.
def flatten(items):
for x in items:
if isinstance(x, list):
yield from flatten(x) # đệ quy gọn
else:
yield x
list(flatten([1, [2, [3, [4, 5]], 6]]))
# [1, 2, 3, 4, 5, 6]
9. send(), throw(), close() — generator hai chiều
Generator không chỉ phát giá trị ra — nó còn nhận giá trị vào. Đây là nền tảng của coroutine
cũ (trước khi async/await chính thức xuất hiện ở Python 3.5).
def echo():
while True:
received = yield # yield không có giá trị bên phải
print(f'Nhận: '{received}')
g = echo()
next(g) # PHẢI prime — chạy đến yield đầu tiên
g.send('hello') # in 'Nhận: hello'
g.send('world') # in 'Nhận: world'
g.close() # raise GeneratorExit bên trong → thoát
Cơ chế: g.send(x) resume generator, biểu thức yield đang pause sẽ trả về
giá trị x. Lưu ý phải next(g) hoặc g.send(None) lần đầu để generator
chạy đến yield đầu tiên (gọi là priming).
def safe_gen():
try:
while True:
x = yield
print(x)
except ValueError:
print('Bắt ValueError trong generator')
finally:
print('Cleanup')
g = safe_gen()
next(g)
g.send(1) # in 1
g.throw(ValueError) # inject exception
# → 'Bắt ValueError trong generator'
# → 'Cleanup'
Từ Python 3.5, async/await đã thay coroutine cũ. Tuy nhiên một số library
(như contextlib.contextmanager sẽ thấy ở chương 7) vẫn dùng generator hai chiều.
close() đặc biệt quan trọng — Python tự gọi nó khi generator bị GC, đảm bảo block
finally luôn chạy để đóng resource.
10. itertools — toolset standard library
itertools là module stdlib chứa các iterator building block. Tất cả đều lazy và
có thể compose với nhau thành pipeline. Đây là bảng tham chiếu ngắn:
| Hàm | Kết quả mẫu | Mô tả |
|---|---|---|
count(5, 2) |
5, 7, 9, 11, ... |
Đếm vô hạn từ start, bước step |
cycle([1,2,3]) |
1, 2, 3, 1, 2, 3, ... |
Lặp vô hạn iterable |
repeat('x', 3) |
'x', 'x', 'x' |
Lặp value n lần (hoặc vô hạn nếu bỏ n) |
chain([1,2], [3,4]) |
1, 2, 3, 4 |
Nối nhiều iterable |
islice(it, 5, 10) |
Phần tử index 5..9 | Slicing cho iterator (không cần list) |
takewhile(p, it) |
Đến khi p(x) false | Lấy prefix thoả điều kiện |
dropwhile(p, it) |
Bỏ qua prefix thoả | Ngược takewhile |
filterfalse(p, it) |
Phần tử p(x) false | Ngược filter |
groupby(it, key) |
(key, group_iter) | Group adjacent phần tử cùng key |
accumulate(it, op) |
1, 3, 6, 10 |
Cumulative reduce (mặc định +) |
combinations(it, r) |
Tổ hợp không trùng | C(n, r) — không phân biệt thứ tự |
permutations(it, r) |
Hoán vị | P(n, r) — phân biệt thứ tự |
product(A, B) |
Tích Đề-các | Thay nested loop |
combinations_with_replacement |
Tổ hợp có lặp | Cho phép chọn lại phần tử |
pairwise(it) |
(1,2), (2,3), (3,4) |
Cặp kề nhau (Py 3.10+) |
compress(it, mask) |
Lọc theo mask boolean | Chọn phần tử ứng với True |
starmap(f, iter) |
f(*args) cho mỗi tuple | Map mà unpack arg |
tee(it, n) |
n iterator giống nhau | "Nhân bản" iterator (cẩn thận memory) |
zip_longest(A, B, fillvalue) |
Zip đến iterable dài nhất | Khác zip built-in (dừng ở ngắn nhất) |
10.1. chain — nối iterable
from itertools import chain
list(chain([1, 2], (3, 4), range(5, 7)))
# [1, 2, 3, 4, 5, 6]
# chain.from_iterable — flatten 1 cấp
list(chain.from_iterable([[1, 2], [3], [4, 5]]))
# [1, 2, 3, 4, 5]
10.2. count / cycle / repeat — vô hạn
from itertools import count, cycle, repeat, islice
# count(start, step) — như range nhưng vô hạn
list(islice(count(10, 3), 5)) # [10, 13, 16, 19, 22]
# cycle — duyệt vòng vô hạn
list(islice(cycle(['A', 'B', 'C']), 7))
# ['A', 'B', 'C', 'A', 'B', 'C', 'A']
# repeat — lặp value
list(repeat('x', 3)) # ['x', 'x', 'x']
list(islice(repeat(0), 4)) # [0, 0, 0, 0]
# Pattern: enumerate kết hợp count với offset
for i, ch in zip(count(100), 'abc'):
print(i, ch) # 100 a, 101 b, 102 c
10.3. islice — slice cho iterator
Không thể gen[5:10] như list — phải dùng islice:
from itertools import islice, count
list(islice(count(), 5)) # [0, 1, 2, 3, 4] stop=5
list(islice(count(), 5, 10)) # [5, 6, 7, 8, 9] start=5, stop=10
list(islice(count(), 0, 10, 2)) # [0, 2, 4, 6, 8] start, stop, step
# LƯU Ý: không hỗ trợ index âm — vì iterator không biết "cuối"
10.4. groupby — group adjacent
Khác SQL GROUP BY! Nếu muốn group toàn bộ, phải sort() trước theo key.
from itertools import groupby
data = [1, 1, 2, 2, 1, 1]
for key, group in groupby(data):
print(key, list(group))
# 1 [1, 1]
# 2 [2, 2]
# 1 [1, 1] ← 3 group vì các nhóm "1" tách biệt
students = [
{'name': 'An', 'class': 'A'},
{'name': 'Bình', 'class': 'B'},
{'name': 'Cường', 'class': 'A'},
{'name': 'Dung', 'class': 'B'},
]
# PHẢI sort theo key trước!
keyfn = lambda s: s['class']
students.sort(key=keyfn)
for cls, group in groupby(students, key=keyfn):
print(cls, [s['name'] for s in group])
# A ['An', 'Cường']
# B ['Bình', 'Dung']
Khi bạn muốn group run-length cùng dạng "đường chạy giống nhau liên tiếp" — ví dụ run-length encoding, phát hiện block log liên tiếp cùng level, hay chia file theo line ngắt — groupby trên dữ liệu chưa sort lại đúng yêu cầu.
10.5. combinations / permutations / product
from itertools import combinations, permutations, product
# combinations — chọn r, KHÔNG quan trọng thứ tự
list(combinations([1, 2, 3], 2))
# [(1, 2), (1, 3), (2, 3)]
# permutations — chọn r, CÓ quan trọng thứ tự
list(permutations([1, 2, 3], 2))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# product — tích Đề-các (thay nested loop)
list(product(['a', 'b'], [1, 2]))
# [('a', 1), ('a', 2), ('b', 1), ('b', 2)]
# product có repeat — duyệt mọi chuỗi length n
list(product([0, 1], repeat=3))
# [(0,0,0), (0,0,1), (0,1,0), ..., (1,1,1)] ← bit pattern length 3
combinations(n, r): C(n,r) — chọn nhóm. "3 trong 10 người, không quan tâm thứ tự".permutations(n, r): P(n,r) — sắp xếp. "Xếp hàng 3 người từ 10".product(A, B): |A| × |B| — bộ đôi từ 2 tập. "Mọi cặp (size, màu)".
10.6. accumulate — cumulative reduce
from itertools import accumulate
import operator
list(accumulate([1, 2, 3, 4]))
# [1, 3, 6, 10] ← prefix sum (mặc định toán tử +)
list(accumulate([1, 2, 3, 4], operator.mul))
# [1, 2, 6, 24] ← prefix product (factorial-like)
list(accumulate([5, 2, 8, 1], max))
# [5, 5, 8, 8] ← running max
# Có thể truyền initial (Py 3.8+)
list(accumulate([1, 2, 3], initial=100))
# [100, 101, 103, 106]
10.7. Compose pipeline
Vẻ đẹp thật sự của itertools là khả năng compose: nối nhiều iterator lại như đường ống.
from itertools import count, islice, takewhile
# Lấy 5 số chẵn đầu > 100
result = list(islice(
(x for x in count(100) if x % 2 == 0),
5
))
# [100, 102, 104, 106, 108]
# takewhile — lấy đến khi false (KHÁC filter ở chỗ dừng hẳn)
result = list(takewhile(lambda x: x < 50, count(40, 2)))
# [40, 42, 44, 46, 48]
Bài tập
Bài 1 — class Countdown với __iter__/__next__
Viết class Countdown:
Countdown(n)tạo iterator đếm ngược từnxuống0.- Implement
__iter__và__next__. - Hỗ trợ
for x in Countdown(5):in 5, 4, 3, 2, 1, 0. - Tự reset mỗi khi gọi
iter()lại (cách tách iterable / iterator).
Đáp án
class Countdown:
def __init__(self, n: int):
self.n = n
def __iter__(self):
return _CountdownIter(self.n)
class _CountdownIter:
def __init__(self, n):
self.current = n
def __iter__(self):
return self
def __next__(self):
if self.current < 0:
raise StopIteration
v = self.current
self.current -= 1
return v
c = Countdown(3)
list(c) # [3, 2, 1, 0]
list(c) # [3, 2, 1, 0] reset OK
Cách generator-based gọn hơn nhiều — chỉ 4 dòng:
class Countdown:
def __init__(self, n): self.n = n
def __iter__(self):
for i in range(self.n, -1, -1):
yield i
Đây là pattern điển hình — để generator tự lo state machine.
Bài 2 — Fibonacci vô hạn + 10 số đầu
Viết generator fibonacci() sinh dãy Fibonacci vô hạn. Sau đó dùng itertools.islice lấy 10 số đầu.
Test:
list(islice(fibonacci(), 10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Đáp án
from itertools import islice
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
list(islice(fibonacci(), 10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Vì sao không hỏng máy? Lazy. yield a pause cho đến khi islice
hỏi giá trị tiếp theo. Sau 10 lần, islice không hỏi nữa — generator chỉ đơn giản
nằm đó cho đến khi bị GC.
Bài 3 — Đọc file lazy bằng generator
Viết read_lines(path) trả generator các dòng đã strip() bỏ ký tự xuống dòng.
Yêu cầu: không load cả file vào RAM (file có thể 10GB).
Bonus: chain với generator khác để đếm số dòng chứa từ khoá 'ERROR'.
Đáp án
def read_lines(path: str):
with open(path, encoding='utf-8') as f:
for line in f:
yield line.rstrip('\n')
# Đếm dòng ERROR — pipeline streaming
err_count = sum(1 for line in read_lines('app.log')
if 'ERROR' in line)
Cách viết thanh lịch hơn dùng yield from:
def read_lines(path):
with open(path, encoding='utf-8') as f:
yield from (line.rstrip('\n') for line in f)
File object Python đã là iterator over lines — không cần readlines() (eager,
load hết). Pattern này là idiomatic Python.
Bài 4 — Producer-consumer với generator
Viết 2 generator:
produce(n)sinh ransố ngẫu nhiên từ 1-100.moving_average(stream, window=3)nhận một iterator số, trả generator của trung bình cộngwindowphần tử gần nhất.
Test: list(moving_average(produce(7), 3)) trả 5 giá trị (đợi đủ 3 phần tử mới có output đầu tiên).
Đáp án
import random
from collections import deque
def produce(n: int):
for _ in range(n):
yield random.randint(1, 100)
def moving_average(stream, window: int = 3):
buf = deque(maxlen=window)
for x in stream:
buf.append(x)
if len(buf) == window:
yield sum(buf) / window
list(moving_average(produce(7), 3))
# [v1, v2, v3, v4, v5] — 7 input → 5 output
Đây là pattern streaming: producer và consumer pause/resume đan xen, không bao giờ tồn tại
cả dataset đầy đủ trong RAM. Cùng cách Python hỗ trợ asyncio queue ở chương 8.
Bài 5 — groupby log theo ngày
Cho list log entries:
logs = [
{'time': '2026-01-15 10:00', 'level': 'INFO', 'msg': 'a'},
{'time': '2026-01-16 08:12', 'level': 'ERROR', 'msg': 'b'},
{'time': '2026-01-15 14:30', 'level': 'INFO', 'msg': 'c'},
{'time': '2026-01-16 09:00', 'level': 'WARN', 'msg': 'd'},
{'time': '2026-01-15 09:00', 'level': 'INFO', 'msg': 'e'},
]
Group theo ngày (10 ký tự đầu của 'time') bằng itertools.groupby. Mỗi nhóm in ngày và số message.
Đáp án
from itertools import groupby
date_of = lambda log: log['time'][:10]
# BẮT BUỘC sort trước theo cùng key
logs.sort(key=date_of)
for day, entries in groupby(logs, key=date_of):
entries = list(entries) # materialize để dùng nhiều lần
print(f'{day}: {len(entries)} entries')
for e in entries:
print(f' [{e["level"]}] {e["msg"]}')
# 2026-01-15: 3 entries
# [INFO] a / [INFO] c / [INFO] e
# 2026-01-16: 2 entries
# [ERROR] b / [WARN] d
Quên sort sẽ ra nhiều group nhỏ thay vì 2 group lớn. Group bằng dict
(collections.defaultdict(list)) là cách thay thế phổ biến khi không cần adjacent.
Quiz
range(10) là iterable hay iterator?
Xem đáp án
Iterable. Có __iter__ trả về một iterator mới mỗi lần gọi, nhưng
không có __next__. Vì vậy r = range(10) rồi list(r)
hai lần đều ra [0..9] — mỗi iter(r) tạo iterator mới.
Đối lập, g = (x for x in range(10)) là iterator (cũng là iterable nhưng __iter__
trả chính nó) — consume xong là cạn.
Generator object có hash được không?
Xem đáp án
Có — generator là object Python, có id() và mặc định hash theo identity.
Tuy nhiên hiếm khi nên dùng generator làm key dict / set vì 2 generator object khác nhau
(dù cùng "định nghĩa") sẽ có hash khác — không có ý nghĩa logic.
Trong thực tế, nếu muốn dùng "sequence" làm key, hãy tuple(gen) để có giá trị immutable
định danh được.
Cho gen = (x*2 for x in range(5)). So sánh:
[x for x in gen]
list(gen)
Kết quả khác hay giống?
Xem đáp án
Giống về kết quả (đều ra [0, 2, 4, 6, 8]) — và đều consume generator.
Cả hai cú pháp đều "drain" generator hết, nên nếu chạy list(gen) sau đó sẽ ra [].
Khác nhau ở style: list(gen) ngắn, idiomatic. [x for x in gen] dài
hơn nhưng dễ thêm filter/map: [x*3 for x in gen if x > 2].
Generator có thể reuse (lặp lại nhiều lần) không?
Xem đáp án
Không. Một khi consume xong, mọi next() tiếp theo đều raise
StopIteration. Sai lầm phổ biến:
data = (x for x in range(5))
print(sum(data)) # 10
print(max(data)) # ValueError — sequence rỗng
Fix: ép sang list (data = list(...)), hoặc viết lại như function trả generator
mới mỗi lần gọi (def make(): yield from range(5)).
So sánh:
yield from sub
for x in sub:
yield x
Hai cái khác nhau ở chỗ nào?
Xem đáp án
Giống về output nếu consumer chỉ next()/iter. Nhưng yield from
còn forward:
send(value)— gửi value vào sub-generator đang pause.throw(exc)— inject exception vào sub-generator.return valuebên trong sub — trở thành giá trị của biểu thứcyield from.
Cách viết dài for x in sub: yield x không truyền được những thứ này — sub-generator
không nhận được send/throw, return value bị mất. Vì vậy với coroutine cũ và compose generator,
yield from là cách chuẩn.
list(groupby([1, 1, 2, 2, 1, 1])) với list() mỗi group, kết quả?
Xem đáp án
3 group: (1, [1, 1]), (2, [2, 2]), (1, [1, 1]).
groupby chỉ gộp phần tử kề nhau cùng key, không tổng hợp toàn cục như SQL.
Muốn 2 group tổng (mọi "1" gộp với nhau), phải sort trước:
data = [1, 1, 2, 2, 1, 1]
sorted_data = sorted(data)
[(k, list(g)) for k, g in groupby(sorted_data)]
# [(1, [1, 1, 1, 1]), (2, [2, 2])]
Đây là gotcha nổi tiếng nhất của itertools.groupby. Quy luật: sort trước, group sau.
Generator nhanh hơn list, đúng hay sai?
Xem đáp án
Phụ thuộc tình huống.
- Sinh 1 lần, hoặc chỉ cần phần đầu (
any,next,breaksớm): generator nhanh hơn — không tốn thời gian build cả list, không tốn RAM. - Đọc nhiều lần / random access / cần length: list nhanh hơn — đã cache sẵn, không phải tính lại. Generator phải regenerate (hoặc cạn).
- Sequence rất lớn / vô hạn / streaming: generator là lựa chọn duy nhất — list không khả thi.
Quy tắc thực tế: start with list comp; switch to gen expr khi gặp vấn đề memory hoặc khi pipe vào hàm
chỉ duyệt một lần (sum, max, any, "".join).
Tổng kết
Sau chương 6, bạn nên đã master:
- Iterable vs Iterator: 2 protocol tách bạch —
__iter__mở phiên,__next__bước. - iter() / next(): built-in để điều khiển tay;
forlà syntactic sugar. - Custom iterable bằng class: 2 pattern — class kiêm iterator (1 lần) vs tách iterable / iterator (lặp lại).
- Generator function:
yield— pause/resume, state machine tự động. - Generator expression:
()thay[]; lazy, O(1) memory. - Lazy vs eager: chọn theo memory/CPU/lặp lại/length/random access.
- Sequence vô hạn:
while True: yield+islice/break. - yield from: delegate, forward send/throw/return.
- send / throw / close: generator 2 chiều — nền tảng coroutine cũ.
- itertools:
chain,count,cycle,repeat,islice,groupby(adjacent!),combinations,permutations,product,accumulate,takewhile,pairwise.
Kết nối
- Chương 7 (Decorators & Context Managers) —
@contextmanagerdùng generator để biến function thành context manager; nhiều decorator pattern (cache, retry) wrap generator function. - Chương 8 (Asyncio & Async) —
async def+yield= async generator;async formở rộng iterator protocol cho coroutine. - Chương 10 (pytest & Testing) — pytest fixture với
yieldchính là generator: code trướcyield= setup, code sau = teardown. - JavaScript Chương 5 — đối chiếu
function*/yieldcủa JS; iterator protocol JS dùng{ value, done }object thay vìStopIterationexception như Python. - Dart Chương 6 —
sync*/yieldDart;IterableDart cũng lazy như generator Python.