Mục lục
Mục tiêu bài học
Sau bài này bạn sẽ:
- Hiểu vì sao cần vòng lặp và bản chất
fortrong Python là lặp qua iterable. - Dùng được
range()với 1, 2, 3 tham số và biếtrangelà lazy. - Lặp qua
str,list,dict. - Dùng
enumerate()vàzip()đúng tình huống. - Phân biệt
breakvàcontinue; biết khốifor ... elsechạy khi nào.
Vì sao cần vòng lặp
Nếu cần in từ 1 đến 5, bạn có thể viết:
print(1)
print(2)
print(3)
print(4)
print(5)
Nhưng nếu là 1 đến 1000? Hay 1 đến 1 triệu (kích cỡ một dataset nhỏ)? Lặp tay không khả thi. Vòng lặp giúp chạy cùng một khối lệnh nhiều lần, mỗi lần với một giá trị khác.
Trong AI / ML, vòng lặp xuất hiện ở khắp nơi: duyệt từng sample trong dataset, duyệt từng epoch khi train model, duyệt từng token trong câu, duyệt từng layer của neural network. Hiểu chắc for là điều kiện đủ để đọc được phần lớn code Python trong các bài sau.
Cú pháp for ... in iterable
Cú pháp tổng quát:
for <biến> in <iterable>:
<khối lệnh thân vòng lặp>
Iterable là một đối tượng mà Python có thể duyệt qua từng phần tử. Ví dụ: str, list, tuple, dict, set, range, file object. Mỗi vòng lặp, Python lấy phần tử kế tiếp gán cho biến rồi chạy thân vòng lặp.
# Lặp qua một list
for n in [10, 20, 30]:
print(n)
# 10
# 20
# 30
Lưu ý quan trọng: for trong Python không phải counter loop như for (i = 0; i < n; i++) trong C / Java. Python không có biến đếm ẩn — bạn chỉ "rút" từng phần tử từ iterable. Muốn có chỉ số, dùng range() hoặc enumerate() (xem mục 4 và 7).
Như mọi khối lệnh khác trong Python, thân vòng lặp thụt vào bằng 4 spaces:
for n in [1, 2, 3]:
square = n * n # thân vòng lặp
print(n, "->", square) # thân vòng lặp
print("Xong") # KHÔNG còn trong vòng lặp
Hàm range()
range() sinh ra một dãy số nguyên — chính là cách quen thuộc để chạy vòng lặp "n lần". Có 3 dạng:
4.1. range(stop) — từ 0 đến stop-1
for i in range(5):
print(i)
# 0
# 1
# 2
# 3
# 4
Chú ý: không bao gồm stop. range(5) sinh 5 số: 0, 1, 2, 3, 4.
4.2. range(start, stop) — từ start đến stop-1
for i in range(2, 6):
print(i)
# 2
# 3
# 4
# 5
4.3. range(start, stop, step) — bước nhảy step
# Số chẵn từ 0 đến 10
for i in range(0, 11, 2):
print(i)
# 0, 2, 4, 6, 8, 10
# step âm để đếm ngược
for i in range(10, 0, -1):
print(i)
# 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
step bằng 0 sẽ báo ValueError. step âm thì start phải lớn hơn stop, nếu không vòng lặp rỗng.
4.4. range là lazy
range() không sinh ngay toàn bộ dãy số ra bộ nhớ. Nó chỉ giữ lại start, stop, step và tính phần tử kế tiếp khi cần:
r = range(10_000_000)
print(r) # range(0, 10000000) — không in ra 10 triệu số
print(type(r)) # <class 'range'>
print(len(r)) # 10000000 — tính trong O(1)
# Muốn có list thật sự thì ép kiểu
print(list(range(5))) # [0, 1, 2, 3, 4]
Đặc tính lazy này giúp for i in range(10_000_000) chạy được mà không tốn ~80MB RAM cho list 10 triệu phần tử.
Lặp qua string
Chuỗi cũng là iterable. Lặp qua chuỗi sẽ trả về từng ký tự:
for ch in "AI":
print(ch)
# A
# I
Dùng để đếm ký tự đặc biệt, ví dụ đếm nguyên âm:
text = "machine learning"
vowels = "aeiou"
count = 0
for ch in text:
if ch in vowels:
count += 1
print(count) # 6
Lặp qua list và dictionary
List và dict sẽ được học chi tiết ở bài 11 và bài 13. Ở đây chỉ cần biết cú pháp lặp:
6.1. Lặp qua list
scores = [8.5, 7.0, 9.2, 6.0]
for s in scores:
print(s)
6.2. Lặp qua dictionary
Mặc định lặp qua dict sẽ trả về các key:
person = {"name": "Alice", "age": 25, "role": "AI Engineer"}
for key in person:
print(key)
# name
# age
# role
Muốn cả key và value, dùng .items():
for k, v in person.items():
print(k, "->", v)
# name -> Alice
# age -> 25
# role -> AI Engineer
Hai phương thức bổ sung: .keys() (chỉ key, tương đương lặp mặc định) và .values() (chỉ value).
enumerate — lấy cả index và value
Khi vừa cần phần tử vừa cần chỉ số, đừng tự tăng biến đếm thủ công. Dùng enumerate():
labels = ["cat", "dog", "bird"]
for i, label in enumerate(labels):
print(i, label)
# 0 cat
# 1 dog
# 2 bird
Có thể truyền tham số start để bắt đầu chỉ số từ một giá trị khác:
for i, label in enumerate(labels, start=1):
print(i, label)
# 1 cat
# 2 dog
# 3 bird
So sánh với cách viết không khuyến nghị:
# Không khuyến nghị — dài và dễ sai
for i in range(len(labels)):
print(i, labels[i])
# Khuyến nghị — gọn và rõ
for i, label in enumerate(labels):
print(i, label)
zip — lặp song song nhiều iterable
zip() ghép từng phần tử cùng vị trí của nhiều iterable thành một tuple:
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 28]
for name, age in zip(names, ages):
print(name, age)
# Alice 25
# Bob 30
# Charlie 28
Tình huống AI / ML quen thuộc: lặp song song feature và label:
features = [[1.2, 0.5], [0.7, 1.8], [2.1, 0.3]]
labels = [0, 1, 0]
for x, y in zip(features, labels):
print("input:", x, "label:", y)
Nếu các iterable không cùng độ dài, zip() dừng ở iterable ngắn nhất (các phần tử dư của iterable dài hơn bị bỏ):
a = [1, 2, 3, 4]
b = ["x", "y"]
print(list(zip(a, b))) # [(1, 'x'), (2, 'y')]
Muốn lỗi rõ ràng khi độ dài khác nhau, từ Python 3.10 có zip(a, b, strict=True) — sẽ báo ValueError.
break và continue
Đôi khi cần thoát vòng lặp sớm hoặc bỏ qua một số phần tử.
9.1. break — thoát vòng lặp ngay lập tức
# Tìm số đầu tiên chia hết cho 7 trong dãy 1..100
for i in range(1, 101):
if i % 7 == 0:
print("Tìm thấy:", i)
break # thoát ngay, không duyệt nữa
9.2. continue — bỏ qua iteration hiện tại, chạy tiếp
# In các số lẻ từ 1 đến 10
for i in range(1, 11):
if i % 2 == 0:
continue # nếu chẵn, bỏ qua phần còn lại
print(i)
# 1
# 3
# 5
# 7
# 9
Cả hai chỉ áp dụng cho vòng lặp gần nhất bao quanh — nếu có lặp lồng nhau, break chỉ thoát lặp trong cùng.
for ... else
Python cho phép gắn một khối else sau for. Khối này chạy chỉ khi vòng lặp kết thúc bình thường (duyệt hết iterable), không chạy nếu thoát bằng break.
# Tìm số chia hết cho 13 trong 1..10
for i in range(1, 11):
if i % 13 == 0:
print("Found:", i)
break
else:
print("Không có số nào chia hết cho 13.")
# Output: Không có số nào chia hết cho 13.
Pattern này hữu ích cho các bài toán "tìm kiếm" — không cần biến cờ (flag) phụ. Tuy nhiên cú pháp dễ gây nhầm với if ... else, nên dùng có chừng mực và viết comment khi cần.
Bài tập
Bài 1: Tính tổng các số nguyên từ 1 đến 100 bằng vòng lặp for. Đáp số đúng là 5050.
Gợi ý: khởi tạo total = 0 trước vòng lặp, mỗi iteration cộng thêm i.
Bài 2: Cho list:
numbers = [3, 8, 12, 5, 7, 14, 9, 22, 1, 6]
Viết vòng lặp in ra tất cả số chẵn trong list. Sau đó in ra tổng các số chẵn.
Bài 3: Cho hai list cùng độ dài:
products = ["sách", "bút", "vở"]
prices = [55000, 8000, 12000]
Dùng zip() và enumerate() để in ra:
1. sách - 55000 VND
2. bút - 8000 VND
3. vở - 12000 VND
Bài 4: Viết vòng lặp duyệt từ 1 đến 30, dừng (break) ngay khi gặp số đầu tiên chia hết cho cả 3 và 5. In ra số đó.
Tóm tắt
for <var> in <iterable>:duyệt qua từng phần tử của iterable, không phải counter loop.range(stop),range(start, stop),range(start, stop, step)— sinh dãy số nguyên, không bao gồmstop;stepcó thể âm.rangelà lazy: không sinh list ngay, chỉ tính phần tử khi cần.- Lặp qua
strtrả về từng ký tự; lặp qualisttrả về từng phần tử; lặp quadictmặc định trả về key, dùng.items()để lấy cả key và value. enumerate(iterable): lấy đồng thời index và value, ưu tiên thay vìrange(len(...)).zip(a, b, ...): lặp song song nhiều iterable, dừng ở iterable ngắn nhất; thêmstrict=Trueđể ép cùng độ dài (Python 3.10+).breakthoát vòng lặp;continuebỏ qua iteration hiện tại;for ... elsechạyelsekhi loop kết thúc mà không gặpbreak.
Bài tiếp theo sẽ học vòng lặp while — lặp khi điều kiện còn đúng, và sâu hơn về break / continue.
