for và danh sách — lặp qua một tập dữ liệu
Khi đã biết trước cần duyệt qua những gì, for gọn hơn while nhiều. Đây cũng là chỗ gặp biến tích luỹ: một ô nhớ lớn dần qua từng vòng để giữ tổng, đếm, hoặc lớn nhất.
TL;DR: for lấy lần lượt từng phần tử của một danh sách và gán vào một biến, chạy thân một lần cho mỗi phần tử, rồi tự dừng khi hết. Nó lo hộ cả ba phần mà while bắt bạn tự viết, nên không có nguy cơ chạy mãi. Điều cần trace ở đây là hai biến với hai vai khác hẳn nhau: biến lặp bị ghi đè mỗi vòng và mất sạch giá trị cũ, còn biến tích luỹ thì cộng dồn và giữ lại dấu vết của mọi vòng trước. Người mới hay gộp hai vai này làm một, và đó là nguồn của phần lớn lỗi khi tính tổng hay đếm.
Ở bài trước, để đi hết một danh sách bằng while, bạn phải tự khởi tạo một biến đếm, tự tăng nó, tự nhớ dừng đúng chỗ. Ba việc, ba cơ hội sai.
Khi bạn đã biết trước cần duyệt qua những gì, có cấu trúc lo hộ cả ba. Bài này giới thiệu nó, cùng với thứ mà mọi chương trình tính tổng, đếm số lượng, hay tìm giá trị lớn nhất đều dùng: biến tích luỹ.
1. Danh sách — một ô nhớ giữ nhiều giá trị
Một ô nhớ có tên giữ đúng một giá trị tại một thời điểm. Danh sách không phá quy tắc đó: ô nhớ vẫn giữ đúng một thứ, chỉ là thứ đó là cả một dãy giá trị có thứ tự.
diem = [7, 9, 5]
print(diem)
print(len(diem))
Output thật:
[7, 9, 5]
3
Cặp ngoặc vuông tạo ra một danh sách. len() cho biết nó có bao nhiêu phần tử. Thứ tự trong danh sách là thứ tự bạn viết, và nó được giữ nguyên — đây là điều for dựa vào.
2. Analogy — chồng hồ sơ trên bàn
Bạn có một chồng hồ sơ và việc phải làm là xử lý từng cái. Bạn nhấc cái trên cùng ra, đặt vào khay đang xử lý, làm xong thì bỏ sang bên, rồi nhấc cái tiếp theo vào đúng cái khay đó. Khay chỉ chứa một hồ sơ tại một lúc, và mỗi lần bạn đặt hồ sơ mới vào là hồ sơ cũ đã rời khỏi khay.
Cạnh đó bạn có một tờ giấy tổng kết. Mỗi hồ sơ xử lý xong, bạn cộng thêm một dòng vào tờ giấy. Tờ giấy không bị xoá đi mỗi lần — nó dày lên.
| Trên bàn | Trong code |
|---|---|
| Chồng hồ sơ | Danh sách |
| Khay đang xử lý | Biến lặp — bị ghi đè mỗi vòng |
| Tờ giấy tổng kết | Biến tích luỹ — cộng dồn qua các vòng |
| Nhấc hồ sơ tiếp theo vào khay | for gán phần tử kế tiếp cho biến lặp |
| Hết chồng thì nghỉ | for tự dừng khi hết danh sách |
Khay thì thay, giấy thì dày. Biến lặp mất giá trị cũ mỗi vòng; biến tích luỹ giữ lại dấu vết của mọi vòng.
3. Biến lặp và biến tích luỹ khác nhau thế nào?
diem = [7, 9, 5]
tong = 0
for d in diem:
tong = tong + d
print(tong)
Output thật:
21
Hai biến, hai vai:

Nhìn hàng d: 7, rồi 9, rồi 5. Mỗi vòng nó bị ghi đè, và giá trị cũ mất hẳn — sau vòng 2, con số 7 không còn ở đâu trong d nữa.
Nhìn hàng tong: 0, 7, 16, 21. Nó không bị ghi đè bằng một giá trị rời rạc mà bằng chính nó cộng thêm cái vừa lấy được. Đó là lý do nó giữ được dấu vết của cả ba vòng, dù mỗi vòng cũng chỉ là một phép gán bình thường.
Hai dòng nữa cùng một khuôn, đổi phép tính thì đổi ý nghĩa:
diem = [7, 9, 5]
dem_dat = 0
lon_nhat = 0
for d in diem:
if d >= 7:
dem_dat = dem_dat + 1
if d > lon_nhat:
lon_nhat = d
print(dem_dat)
print(lon_nhat)
Output thật:
2
9
dem_dat cộng 1 mỗi khi gặp phần tử thoả điều kiện — đó là đếm. lon_nhat ghi đè bằng phần tử hiện tại mỗi khi gặp cái lớn hơn — đó là tìm lớn nhất. Cả hai đều là biến tích luỹ, đều phải khởi tạo trước vòng lặp.
Khởi tạo tong = 0 vì cộng thêm 0 không đổi gì. Nhưng nếu bạn tính tích thì phải khởi tạo bằng 1, vì nhân với 0 sẽ ra 0 mãi mãi. Còn lon_nhat = 0 chỉ đúng khi mọi phần tử đều không âm — với danh sách toàn số âm, kết quả sẽ là 0, một con số không hề có trong danh sách.
4. Thử đoán — đổi chỗ một dòng
Đoạn dưới giống hệt đoạn tính tổng ở mục 3, chỉ khác đúng một chỗ: dòng khởi tạo tong = 0 bị đẩy vào trong thân vòng lặp.
diem = [7, 9, 5]
for d in diem:
tong = 0
tong = tong + d
print(tong)
Chương trình in ra số mấy? Viết ra giấy, kèm một câu nói rõ tong bằng bao nhiêu ở đầu mỗi vòng.
Output thật:
5
Không phải 21, mà là 5 — đúng bằng phần tử cuối cùng của danh sách.
Vì dòng tong = 0 giờ nằm trong thân, nó chạy lại ở đầu mỗi vòng. Mọi thứ tích được ở vòng trước bị xoá sạch ngay trước khi vòng mới cộng vào:
| Vòng | d | tong sau tong = 0 | tong sau khi cộng |
|---|---|---|---|
| 1 | 7 | 0 | 7 |
| 2 | 9 | 0 | 9 |
| 3 | 5 | 0 | 5 |
Biến tích luỹ mất vai tích luỹ và tụt xuống thành một biến lặp thứ hai. Đây là lý do vị trí dòng khởi tạo quan trọng ngang với chính phép cộng: nó phải nằm ngoài vòng lặp, chạy đúng một lần.
5. Tới lượt bạn — sắp lại một vòng lặp tích luỹ
Sắp các dòng thành chương trình cộng tiền ba món hàng rồi in tổng.
Output mong đợi:
95000
Đưa từng dòng vào khung lời giải rồi xếp cho đúng thứ tự. Kho có thể chứa dòng thừa.
Kho dòng (7)
tong = 0tong = tong + gprint(tong)print(tong)gia = [30000, 45000, 20000]tong = 0for g in gia:
Lời giải của bạn (0/5)
Chưa có dòng nào. Bấm “Đưa vào” ở trên.
Gợi ý: Hai dòng nhiễu đều là dòng ĐÚNG nhưng đặt sai chỗ — chúng nằm trong thân vòng lặp. Hỏi từng dòng: cái này phải chạy một lần, hay chạy lại mỗi vòng?
Hai dòng nhiễu ở đây tinh vi hơn bài trước: chúng không sai về nội dung, chỉ sai về vị trí. tong = 0 trong thân là đúng cái bẫy bạn vừa gặp ở mục 4; còn print(tong) trong thân thì in ra ba dòng thay vì một.
6. Tới lượt bạn — điền nốt bảng trace
Cột là biến, hàng là dòng lệnh vừa chạy xong. Với vòng lặp, bảng dài ra theo số vòng: cùng một dòng code xuất hiện nhiều hàng, mỗi hàng một vòng.
so = [4, 8, 6]
tong = 0
dem = 0
for s in so:
tong = tong + s
dem = dem + 1
print(tong)
print(dem)
| Vòng | s | tong sau vòng | dem sau vòng |
|---|---|---|---|
| trước vòng lặp | – | 0 | 0 |
| 1 | 4 | 4 | 1 |
| 2 | … | … | … |
| 3 | … | … | … |
Chép bảng ra giấy, điền vòng 2 và vòng 3. Rồi trả lời thêm: chương trình in ra hai số nào, và nếu danh sách rỗng thì hai số đó là bao nhiêu?
| Vòng | s | tong sau vòng | dem sau vòng |
|---|---|---|---|
| trước vòng lặp | – | 0 | 0 |
| 1 | 4 | 4 | 1 |
| 2 | 8 | 12 | 2 |
| 3 | 6 | 18 | 3 |
Chương trình in ra 18 rồi 3.
Với danh sách rỗng, thân vòng lặp không chạy lần nào — for chỉ đơn giản là hết việc ngay. Hai số in ra sẽ là 0 và 0, đúng bằng giá trị khởi tạo. Đây là chỗ giá trị khởi tạo lộ ra vai trò thật của nó: nó là câu trả lời đúng cho trường hợp không có dữ liệu nào.
7. Bẫy thường gặp khi dùng for
❌ Nhầm 1 — khởi tạo biến tích luỹ trong thân. Mỗi vòng xoá sạch kết quả vòng trước, và kết quả cuối cùng chỉ phản ánh phần tử cuối.
✅ Dòng khởi tạo nằm ngoài, ngang hàng lề với for, chạy đúng một lần.
❌ Nhầm 2 — dùng biến lặp sau khi vòng lặp kết thúc. Sau vòng cuối, biến lặp vẫn còn giá trị của phần tử cuối cùng, nên code chạy được và cho ra một con số trông hợp lý. Nhưng con số đó chỉ là phần tử cuối, không phải kết quả của cả danh sách.
✅ Kết quả của cả vòng lặp phải nằm trong biến tích luỹ, không nằm trong biến lặp.
8. 📚 Đào sâu — for trong tài liệu Python
4. More Control Flow Tools — Python documentation — mục "for Statements" cho thấy đúng khuôn bạn vừa học, với ví dụ duyệt một danh sách chuỗi. Đáng đọc vì nó nói rõ for của Python duyệt các phần tử của một dãy theo đúng thứ tự chúng xuất hiện, và tự đặt mình đối lập với hai kiểu for khác: kiểu của Pascal luôn chạy qua một dãy số cách đều, còn kiểu của C thì để người viết tự định nghĩa cả bước nhảy lẫn điều kiện dừng. Đó là lý do for ở đây không thể chạy mãi — nó không hỏi một điều kiện do bạn viết, nó chỉ đi hết một dãy hữu hạn có sẵn.
9. Liên hệ các bài khác
- Bài 3 — Vòng lặp while — ba phần bạn phải tự viết ở đó chính là ba thứ
forlo hộ tại đây. - Bài 4 module trước — Bảng trace — bảng trace của vòng lặp dài ra theo số vòng, cùng một dòng code xuất hiện nhiều hàng.
- Bài 5 — Lặp lồng nhau — đặt một
forvào trong mộtfor, và biến tích luỹ ở đó phải chọn nằm ở tầng nào. - Bài 6 — Mini challenge — nơi bạn tự trace mà không có bảng điền sẵn nào.
10. Tóm tắt
- Nghi kết quả tính tổng sai? Soi dòng khởi tạo trước: nằm trong hay ngoài
forquyết định biến đó còn là biến tích luỹ hay không. - Tổng ra đúng bằng phần tử cuối cùng? Đó là chữ ký của dòng khởi tạo bị đặt lọt vào trong thân.
- Kết quả là một con số không hề có trong danh sách? Kiểm giá trị khởi tạo —
lon_nhat = 0gặp dữ liệu toàn số âm sẽ ra đúng như vậy. - Chưa biết khởi tạo bằng mấy? Hỏi "danh sách rỗng thì đáp án đúng là gì" — câu trả lời đó chính là giá trị cần điền.
- Cần kết quả của cả danh sách mà lại đọc biến lặp sau vòng lặp? Nó chỉ còn phần tử cuối, và điều đó không báo lỗi.
11. Tự kiểm tra
- Q1Biến lặp và biến tích luỹ khác nhau ở điểm nào? Nêu bằng cơ chế phép gán, đừng chỉ nói một cái đổi một cái không.
- Q2Vì sao đặt
tong = 0vào trong thân vòng lặp làm kết quả thành 5 thay vì 21? - Q3Danh sách rỗng thì thân vòng lặp chạy mấy lần, và chương trình tính tổng in ra số mấy?
- Q4Với
diem = [7, 9, 5], ngay sau khi vòng thứ hai chạy xong thìdvàtonglần lượt bằng bao nhiêu? Vì sao từ vòng hai trở đi hai con số này không còn bằng nhau? - Q5Vì sao vòng
fortrong bài không thể chạy mãi, trong khiwhileở bài trước thì có thể? - Q6Sau khi vòng lặp kết thúc, biến lặp còn giữ giá trị gì? Vì sao dùng nó ở đó là một lỗi khó phát hiện?
Bài tiếp theo: Lặp lồng nhau và lỗi lệch một đơn vị
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên