Câu 1Cách ngắn nhất để xây tích Descartes của [1, 2, 3] và [A, B] là gì?
itertools và functools — Bộ công cụ lặp và kết hợp hàm
Học các công thức lặp combinations/product/chain/accumulate, cố định đối số với partial, và memoize đệ quy với @lru_cache để tăng tốc, qua ví dụ thật.
itertools là tập các hàm xây iterable mới từ iterable đã có (kết hợp, nối, tích lũy), còn functools là tập các hàm biến đổi chính bản thân hàm (gắn đối số, memoize). Cả hai đều có thể thay thế pattern for loop + tự theo dõi state trong một dòng, làm code ngắn đi rất nhiều.
Iterable — giá trị bạn có thể lặp qua
Hầu hết hàm itertools nhận "iterable" làm input và trả về iterable mới, nên đáng để xác định iterable là gì trước. Iterable (thứ bạn có thể viết bên phải for x in ___:) là bất cứ gì bạn có thể truyền cho vòng for, list(...), sum(...), hoặc map(...). list / tuple / str / dict / set / range, cộng với generator bạn tự viết (hàm dùng yield), tất cả đều hoạt động như iterable trong Python.
list / tuple / str / dict / set / range đều là iterable, và generator tự xây với yield cũng vậy.itertools — bộ công cụ kết hợp và lặp
itertools là module tập hợp các hàm xây iterable mới từ iterable đã có. Bốn hàm bạn dùng nhiều nhất là combinations (kết hợp không thứ tự), product (tích Descartes qua nhiều tập), chain (nối các iterable thành một), và accumulate (tổng cộng dồn hoặc tích cộng dồn). Biết bốn hàm này cho phép bạn viết lại vòng for lồng nhau và vòng lặp tự theo dõi state gọn hơn nhiều.
list(...) để vật chất hóa.| Hàm | Input → Output | Ví dụ |
|---|---|---|
| combinations(it, k) | Kết hợp k phần tử (không thứ tự) | [A,B,C] → (A,B), (A,C), (B,C) |
| permutations(it, k) | Hoán vị k phần tử (có thứ tự) | [A,B] → (A,B), (B,A) |
| product(*its) | Tích Descartes qua các tập | [S,M] × [red,blue] → 4 cặp |
| chain(*its) | Nối nhiều iterable | [1,2] + [3,4] → [1,2,3,4] |
| accumulate(it) | Cộng dồn từ trái (mặc định là sum) | [10,20,30] → [10, 30, 60] |
functools.partial — gắn đối số trước
functools.partial xây một hàm mới với một số đối số đã được khóa sẵn. Khi bạn cần truyền hàm với đối số đã gắn cho callback hoặc hàm bậc cao, partial giúp bạn khỏi viết các hàm wrapper một dòng kiểu def wrapper(...): .... Bất cứ chỗ nào bạn gọi cùng một hàm lặp đi lặp lại với biến thể nhỏ trong đối số, partial cắt bỏ tiếng ồn wrapper và làm code dễ đọc hơn.
from functools import partial
def format_with_unit(price, unit):
return f"{price}{unit}"
# Xây hàm mới với đơn vị "USD" được gắn sẵn
to_usd = partial(format_with_unit, unit="USD")
# Áp dụng cho từng giá — chỉ cần truyền price
print(to_usd(100)) # 100USD
print(to_usd(200)) # 200USD
print(to_usd(300)) # 300USD
exp=2 trên power(base, exp) và bạn được hàm square chỉ cần base.functools.lru_cache — cache kết quả với memoize
"Tôi muốn tái sử dụng kết quả của một hàm tốn kém được gọi lặp với cùng đối số" — xuất hiện bất cứ khi nào output của hàm được xác định bởi input và tốc độ là quan trọng. Tự xây nghĩa là tự quản dict cache của riêng bạn, nhưng @lru_cache làm điều đó trong một dòng decorator.
@lru_cache là decorator cache giá trị trả về của hàm. Khi gọi lại với cùng đối số, nó trả về giá trị cache trực tiếp, bỏ qua tính toán lại. LRU (Least Recently Used — bỏ các entry không dùng lâu nhất) bỏ entry cache cũ nên bạn có thể giới hạn dùng bộ nhớ với cái gì đó như maxsize=128.
Đừng thêm vào hàm có side effect
@lru_cache giả định "cùng đối số → cùng giá trị trả về". Áp dụng nó cho hàm có side effect (đọc file / ghi DB / trả về thời gian hiện tại) hoặc kết quả thay đổi cho cùng input, và bạn sẽ có bug kết quả đầu tiên cứ trả về mãi mãi. Chỉ áp dụng cho hàm thuần (cùng input → cùng output, không side effect).
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2partial(f, x=10) trả về cái gì?
Câu 3Tại sao @lru_cache tăng tốc Fibonacci đệ quy ngây thơ?
Câu 4Khi gọi accumulate([10, 20, 30]), bạn nhận được gì?