DHDataHọc
Lý thuyếtBài 13/305 phút đọc+10 XP

Vòng lặp for

Bạn sẽ học được

  • Cách for duyệt qua list, chuỗi, dict và range.
  • enumerate khi cần cả chỉ số, zip khi cần duyệt song song.
  • Mẫu cộng dồn — nền tảng của mọi phép tổng hợp dữ liệu.
  • Vòng lặp lồng nhau và for ... else.
  • Vì sao không nên sửa list đang duyệt.

Khái niệm cốt lõi

for trong Python duyệt qua một iterable — bất cứ thứ gì có thể lấy lần lượt từng phần tử: list, tuple, chuỗi, dict, file, range. Khác nhiều ngôn ngữ, không có biến đếm và điều kiện; Python tự lấy phần tử tiếp theo cho đến hết.

code
names = ["An", "Bình", "Chi"]
for name in names:
    print(name)
code
An
Bình
Chi

Biến name lần lượt nhận từng phần tử. Sau vòng lặp, nó vẫn giữ giá trị cuối ("Chi") — Python không xoá biến sau for. Đừng dựa vào điều này; nếu cần giá trị cuối, hãy gán tường minh.

Chuỗi cũng là iterable, duyệt từng ký tự:

code
for ch in "data":
    print(ch)
code
d
a
t
a

range — sinh dãy số

range không tạo list thật mà sinh giá trị lười, nên rất tiết kiệm bộ nhớ:

code
print(list(range(1, 6)))
print(list(range(0, 10, 3)))
print(list(range(5, 0, -1)))
code
[1, 2, 3, 4, 5]
[0, 3, 6, 9]
[5, 4, 3, 2, 1]

Cú pháp range(start, stop, step): start tính cả, stop không tính, step có thể âm. Chỉ truyền một tham số thì đó là stop và start = 0. Đây là nguồn gốc lỗi "thiếu một phần tử": range(5) cho 0..4, không phải 1..5.

Khi chỉ cần lặp đúng N lần mà không dùng số, đặt tên _:

code
for _ in range(3):
    print("Xin chào")

enumerate — chỉ số và giá trị

Không nên viết for i in range(len(names)) rồi names[i]. Dùng enumerate:

code
names = ["An", "Bình", "Chi"]
for i, name in enumerate(names, start=1):
    print(i, name)
code
1 An
2 Bình
3 Chi

start=1 đổi điểm bắt đầu; mặc định là 0. enumerate trả cặp (chỉ số, giá trị) mỗi vòng, gọn và không cần truy cập theo chỉ số.

zip — duyệt song song

Khi có hai dãy cùng độ dài:

code
cities = ["Hà Nội", "Đà Nẵng", "Cần Thơ"]
sales = [120, 85, 64]
for city, amount in zip(cities, sales):
    print(f"{city}: {amount} triệu")
code
Hà Nội: 120 triệu
Đà Nẵng: 85 triệu
Cần Thơ: 64 triệu

zip dừng ở dãy ngắn nhất, không báo lỗi khi lệch độ dài. Nếu cần phát hiện lệch, so len() trước hoặc dùng zip(..., strict=True) từ Python 3.10 — khi đó ném ValueError nếu độ dài khác nhau. Trong phân tích dữ liệu, lệch độ dài thường là dấu hiệu dữ liệu đã bị lọc sai, nên strict=True đáng dùng.

Mẫu cộng dồn

Đây là khuôn mẫu quan trọng nhất của chương — mọi phép tính tổng, đếm, nhóm sau này đều dựa trên nó:

code
sales = [120, 85, 64]
total = 0
for amount in sales:
    total += amount
print(total)
code
269

Ba bước: khởi tạo biến tích luỹ trước vòng lặp, cập nhật trong vòng lặp, dùng kết quả sau vòng lặp. Nếu khởi tạo bên trong, mỗi vòng sẽ reset và kết quả chỉ còn phần tử cuối.

Các biến thể:

code
count = 0
for amount in sales:
    if amount >= 80:
        count += 1

average = total / len(sales)
print(count, average)
code
2 89.66666666666667

len(sales) bằng 0 sẽ gây ZeroDivisionError; dữ liệu thật luôn cần kiểm tra danh sách rỗng trước khi chia.

Vòng lặp lồng nhau

code
matrix = [[1, 2], [3, 4]]
for row in matrix:
    for value in row:
        print(value, end=" ")
    print()
code
1 2
3 4

Vòng ngoài đi qua từng hàng, vòng trong đi qua từng phần tử. Với ma trận lớn, chi phí tăng theo tích hai kích thước; nếu chỉ cần duyệt phẳng, một vòng với hai for trong comprehension sẽ gọn hơn (bài cuối chương).

for ... else

Python có nhánh else chạy khi vòng lặp kết thúc bình thường, không bị break:

code
numbers = [2, 4, 6, 7]
for n in numbers:
    if n % 2 == 1:
        print("Tìm thấy số lẻ:", n)
        break
else:
    print("Toàn số chẵn")
code
Tìm thấy số lẻ: 7

Nếu list toàn số chẵn, nhánh else chạy. Cú pháp này hay gây nhầm vì else trông như "ngược lại của if", nhưng nghĩa thực là "không có break". Nhiều người thấy khó đọc và chuyển sang dùng cờ found = False. Cả hai đều chấp nhận; quan trọng là nhất quán trong toàn dự án.

Không sửa list đang duyệt

code
nums = [1, 2, 3, 4]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)
code
[1, 3]

Kết quả có vẻ đúng, nhưng đây là may mắn: khi xoá phần tử, chỉ số của các phần tử sau dịch sang trái, nên vòng lặp có thể bỏ qua phần tử kế tiếp. Với [2, 4, 6], chỉ 2 bị xoá và 4 bị bỏ qua:

code
nums = [2, 4, 6]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)
code
[4]

Cách đúng là tạo list mới bằng comprehension, hoặc duyệt trên bản sao for n in nums.copy():. Tạo list mới thường rõ ràng hơn:

code
nums = [2, 4, 6]
nums = [n for n in nums if n % 2 != 0]
print(nums)

Thực hành

  1. In các thành phố và doanh số bằng zip, định dạng f-string.
  2. Dùng enumerate(start=1) in bảng đánh số.
  3. Tính tổng và trung bình của một list doanh số, kiểm tra list rỗng.
  4. Duyệt một dict và in key = value (dict sẽ học kỹ ở bài 4, nhưng for key in d đã chạy được).
  5. Duyệt ma trận 3×3 và in từng hàng.
  6. Tái hiện lỗi xoá phần tử khi đang duyệt với [2, 4, 6], rồi sửa bằng comprehension.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Thiếu phần tử cuốirange(1, n) không gồm nDùng range(1, n + 1)
IndexError trong vòng lặpDuyệt range(len(x)) rồi truy cập saiDùng for x in ... hoặc enumerate
Tổng luôn bằng phần tử cuốiKhởi tạo biến trong vòng lặpKhởi tạo trước vòng lặp
Mất phần tử khi lọcSửa list đang duyệtTạo list mới bằng comprehension
zip bỏ sót phần tửHai dãy lệch độ dàiKiểm tra len hoặc strict=True
ZeroDivisionErrorChia cho len của list rỗngKiểm tra rỗng trước
else chạy ngoài ý muốnHiểu nhầm for ... elseNhớ: else chạy khi không có break

Tóm tắt

  • for duyệt iterable trực tiếp; không cần biến đếm.
  • range(start, stop, step) không gồm stop.
  • enumerate cho chỉ số, zip cho duyệt song song (dừng ở dãy ngắn nhất).
  • Mẫu cộng dồn: khởi tạo trước, cập nhật trong, dùng sau.
  • Không sửa collection đang duyệt — tạo bản mới.

Dùng ← → để chuyển bài