Iterator: Cơ chế lặp không phụ thuộc chỉ số
Iterator (trình lặp) là một giao diện chuẩn để truy cập tuần tự các phần tử trong một tập hợp mà không cần biết cấu trúc nội bộ của nó. Khác với vòng lặp while đơn thuần — chỉ lặp vô hạn mà không thay đổi trạng thái — việc lặp thực sự yêu cầu mỗi bước phải tạo ra giá trị mới làm đầu vào cho bước tiếp theo:
# Không phải lặp thực sự (không có trạng thái chuyển tiếp)
while True:
print("→")
# Đây mới là lặp: sử dụng chỉ số như trạng thái
data = [10, 20, 30]
index = 0
while index < len(data):
print(data[index])
index += 1
Tại sao cần Iterator?
Các kiểu dữ liệu tuần tự như list, str, tuple hỗ trợ truy cập bằng chỉ số, nhưng dict, set hay file object thì không. Iterator cung cấp cơ chế thống nhất để duyệt qua mọi đối tượng — bất kể có chỉ số hay không.
Phân biệt Iterable và Iterator
- Iterable: Bất kỳ đối tượng nào triển khai phương thức
__iter__(), ví dụ:"abc".__iter__(),{'x':1}.__iter__(),open("log.txt").__iter__(). - Iterator: Đối tượng vừa có
__iter__()vừa có__next__(). Gọiiter(obj)trên một iterable sẽ trả về iterator.
Lưu ý: Mọi iterator đều là iterable, nhưng ngược lại thì không đúng.
Sử dụng Iterator thủ công
config = {"host": "localhost", "port": 8080, "debug": True}
it = iter(config) # Tương đương config.__iter__()
print(next(it)) # "host"
print(next(it)) # "port"
print(next(it)) # "debug"
# next(it) → StopIteration (tín hiệu kết thúc)
# Duyệt an toàn bằng try/except
it = iter(config)
while True:
try:
key = next(it)
print(f"{key} = {config[key]}")
except StopIteration:
break
Vai trò của vòng lặp for
Vòng lặp for tự động xử lý toàn bộ quy trình iterator:
- Gọi
iter(iterable)để lấy iterator. - Gọi liên tục
next(iterator)cho đến khi gặpStopIteration. - Mỗi giá trị được gán vào biến điều khiển và thực thi khối lệnh.
for key in config:
print(f"{key}: {config[key]}")
Ưu – nhược điểm của Iterator
- Ưu điểm: Tính nhất quán cao, tiết kiệm bộ nhớ nhờ đánh giá chậm (lazy evaluation), phù hợp với dữ liệu lớn hoặc luồng vô hạn.
- Nhược điểm: Không hỗ trợ truy cập ngẫu nhiên, không biết trước độ dài, chỉ duyệt một lần và không thể quay lui.
Generator: Iterator được tạo động bằng yield
Generator là một dạng đặc biệt của iterator, được định nghĩa bằng hàm chứa từ khóa yield. Khi gọi hàm generator, Python không thực thi thân hàm mà trả về một đối tượng generator — tức là một iterator đầy đủ chức năng.
def countdown(n):
while n > 0:
yield f"Đếm ngược: {n}"
n -= 1
yield "Khởi động!"
engine = countdown(3)
print(type(engine)) # <class 'generator'>
print(next(engine)) # "Đếm ngược: 3"
print(next(engine)) # "Đếm ngược: 2"
Generator kế thừa đầy đủ hành vi của iterator: hỗ trợ iter(), next(), và hoạt động mượt mà trong for.
Ứng dụng thực tế
1. Triển khai lại range tùy chỉnh:
def custom_range(start, stop, step=1):
current = start
while current < stop:
yield current
current += step
for x in custom_range(5, 15, 3):
print(x) # 5, 8, 11, 14
2. Xử lý luồng log theo thời gian thực:
import time
def follow_log(filepath):
with open(filepath, "r") as f:
f.seek(0, 2) # Di chuyển tới cuối file
while True:
line = f.readline()
if line:
yield line.rstrip()
else:
time.sleep(0.1)
def filter_404(lines):
for line in lines:
if "404" in line:
yield line
# Kết nối các generator như ống dẫn
for match in filter_404(follow_log("access.log")):
print(f"[404] {match}")
Generator với giao tiếp hai chiều: send()
Khi dùng yield như một biểu thức, generator có thể nhận dữ liệu từ bên ngoài qua phương thức send():
def meal_planner():
menu = []
print("Chuẩn bị nhận món...")
while True:
dish = yield menu
if dish is not None:
menu.append(dish)
print(f"✓ Đã thêm: {dish}")
planner = meal_planner()
next(planner) # Khởi tạo (bắt buộc trước send)
planner.send("Cơm chiên")
planner.send("Canh chua")
print(planner.send("Trà đá")) # ['Cơm chiên', 'Canh chua', 'Trà đá']
Decorator khởi tạo tự động cho generator
Để tránh gọi next() thủ công, ta xây dựng decorator đảm bảo generator luôn ở trạng thái sẵn sàng nhận dữ liệu:
def auto_start(func):
def wrapper(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # Khởi động ngay lập tức
return gen
return wrapper
@auto_start
def processor():
buffer = []
while True:
item = yield buffer
if item:
buffer.append(item.upper())
proc = processor()
print(proc.send("hello")) # ['HELLO']
Lập trình Hướng Thủ tục: Tư duy dòng chảy
Hướng thủ tục (procedural programming) không đơn thuần là viết nhiều hàm — mà là cách tư duy dựa trên chuỗi các bước giải quyết vấn đề theo thứ tự logic. Mô hình này giống như thiết kế một dây chuyền sản xuất: mỗi giai đoạn xử lý một phần công việc và truyền kết quả sang giai đoạn kế tiếp.
Ví dụ điển hình:
- Dây chuyền đăng nhập: Nhập tài khoản → xác thực → tải giao diện người dùng.
- Dây chuyền xử lý truy vấn: Nhận câu lệnh SQL → phân tích cú pháp → tối ưu hóa → thực thi.
Đánh giá mô hình
- Ưu điểm: Rõ ràng, dễ hiểu, hiệu suất cao, phù hợp với hệ thống nhúng hoặc nhân điều hành (Linux kernel, Git, Nginx).
- Nhược điểm: Khó mở rộng — thay đổi một bước thường kéo theo sửa nhiều nơi; thiếu tính đóng gói và tái sử dụng cao như hướng đối tượng.
Do đó, hướng thủ tục vẫn giữ vai trò then chốt trong các hệ thống yêu cầu kiểm soát chặt, hiệu năng tối ưu và độ tin cậy cao — nơi tính rõ ràng và khả đoán của luồng điều khiển là ưu tiên hàng đầu.