Hiểu sâu về Iterator, Generator và Lập trình Hướng Thủ tục trong Python

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ọi iter(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:

  1. Gọi iter(iterable) để lấy iterator.
  2. Gọi liên tục next(iterator) cho đến khi gặp StopIteration.
  3. 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.

Thẻ: python Iterator generator procedural-programming yield

Đăng vào ngày 28 tháng 6 lúc 20:06