0
0
Lập trình
TT

Iterators: Người Phục Vụ Một Chiều Trong Lập Trình Python

Đăng vào 7 giờ trước

• 6 phút đọc

🍽️ Khám Phá Iterators Trong Python

Trong phần 1, chúng ta đã làm quen với buffet (iterables) - nguồn thực phẩm vô tận. Nhưng buffet không tự phục vụ được. Bạn cần một người phục vụ đi dọc theo hàng, nhớ nơi bạn đã dừng lại, và đưa cho bạn món ăn tiếp theo.

Đó chính là iterator:
👉 Một người phục vụ với vé một chiều không thể quay lại.

📋 Điều Bạn Sẽ Học Được

Cuối bài viết này, bạn sẽ biết:

  • Thế nào là một iterator (__iter____next__).
  • Cách StopIteration thực sự kết thúc một vòng lặp for.
  • Cách CPython đại diện cho các iterator trong bộ nhớ (nhẹ, dựa trên con trỏ).
  • Tại sao generators chỉ là những người phục vụ tinh vi nhờ vào yield.
  • Những điều thú vị, cạm bẫy và mẹo gỡ lỗi.

Hãy lấy một đĩa, chúng ta cùng bắt đầu.


🍴 Mở đầu Cuộc Sống Của Người Phục Vụ

Hãy tưởng tượng bạn đang ở buffet:

  • Buffet (iterable) nói: “Đây là một người phục vụ.”
  • Người phục vụ (iterator) nói: “Để tôi phục vụ bạn món đầu tiên.”
  • Mỗi khi bạn gọi next(), người phục vụ bước thêm một bước.
  • Khi không còn thực phẩm? Người phục vụ bỏ khay và hét lên: StopIteration!

Điểm Chú Ý:

👉 Người phục vụ là có trạng thái. Anh ta nhớ nơi anh đã dừng lại.


⚙️ Iterator Là Gì? (Hợp Đồng)

Python định nghĩa một iterator với 2 quy tắc đơn giản:

Copy
__iter__()   # phải trả về self
__next__()   # phải trả về món tiếp theo, hoặc raise StopIteration

Ví Dụ:

Copy
class Countdown:
    def __init__(self, start):
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        val = self.current
        self.current -= 1
        return val

c = Countdown(3)
print(list(c))  # [3, 2, 1]

👉 Lưu ý điều kỳ lạ: __iter__() trả về self.
Bởi vì người phục vụ vừa là iterable vừa là iterator.

Đó là lý do tại sao các iterator là một lần - khi đã hết, chúng không còn sử dụng được nữa.


🧠 Cấu Trúc Nội Tại Của Các Đối Tượng Iterator Trong CPython

Trong CPython (phiên bản tham chiếu của Python):

  • Một đối tượng list_iterator có:
    • ob_ref: con trỏ đến danh sách gốc.
    • index: một con trỏ số nguyên (bắt đầu từ 0).

Mỗi lần gọi next() sẽ:

  1. Nhìn vào list[index].
  2. Tăng chỉ số lên.
  3. Trả về đối tượng.
  4. Nếu index >= len(list), raise StopIteration.

Chi Phí Bộ Nhớ:

  • Iterator chỉ là một cấu trúc nhỏ (con trỏ + chỉ số).
  • Nó không sao chép danh sách.

Đó là lý do tại sao các iterator rất nhẹ - chỉ vài byte thay vì sao chép dữ liệu của bạn.


🛑 StopIteration - “Xin lỗi, không còn thực phẩm”

Khi một iterator kết thúc, nó raise StopIteration.

Nhưng bạn hầu như không bao giờ thấy nó vì các vòng lặp for và comprehension xử lý nó một cách êm ái.

Ví Dụ:

Copy
it = iter([1, 2])
while True:
    try:
        item = next(it)
    except StopIteration:
        break
    print(item)

Đó là những gì for x in [1,2]: biên dịch thành.
StopIterationtín hiệu để phá vỡ vòng lặp.


🔬 Phân Tích: Iterables So Với Iterators

Hãy tóm tắt với các phép ẩn dụ về buffet:

Đối Tượng Ai Là Ai Trong Nhà Hàng? Giao Thức Nhiều Lần?
Iterable Bàn buffet __iter__ Có (người phục vụ mới mỗi lần)
Iterator Người phục vụ với đĩa __iter__ + __next__ Không (một chuyến duy nhất)

⚡ Generators: Người Phục Vụ Tự Động

Việc viết __next__ bằng tay có vẻ hơi khó khăn. Đó là lý do tại sao Python cho chúng ta generators.

Một generator chỉ là một hàm đặc biệt với yield ghi nhớ trạng thái của nó giữa các lần gọi.

Copy
def countdown(n):
    while n > 0:
        yield n   # tạm dừng tại đây
        n -= 1

c = countdown(3)
print(next(c))  # 3
print(next(c))  # 2
print(next(c))  # 1
print(next(c))  # StopIteration

Cách Hoạt Động:

  • Khi bạn gọi countdown(3), Python tạo ra một đối tượng generator.
  • Đối tượng đó có một khung ngăn xếp và một con trỏ hướng dẫn.
  • Mỗi lần gọi next() sẽ tiếp tục thực hiện cho đến khi gặp yield tiếp theo.

Nó giống như một người phục vụ với một điểm lưu trong thời gian.


🔍 Thực Tế Thú Vị: Iterators Có Mặt Khắp Nơi

  • Tệpfor line in open('file.txt'): là một iterator qua các dòng.
  • Từ điểnfor key in d: lặp qua các khóa một cách lười biếng.
  • range → trả về một range_iterator, không tạo danh sách khổng lồ trong bộ nhớ.
  • zip, map, filter → tất cả đều trả về iterators.
  • itertools → nhà máy sản xuất iterators vô hạn hoặc lười biếng.

Về cơ bản: bất cứ khi nào Python có thể tránh việc tạo ra một danh sách khổng lồ, nó sử dụng iterator.


💾 So Sánh Bộ Nhớ: danh sách, iterator và generator

Copy
import sys

nums = [i for i in range(1_000_000)]
print(sys.getsizeof(nums))  # ~8 MB

gen = (i for i in range(1_000_000))
print(sys.getsizeof(gen))   # ~112 bytes 🤯

👉 Một danh sách giữ tất cả 1 triệu tham chiếu trong bộ nhớ.
👉 Một generator chỉ lưu trữ một khung đối tượng nhỏ.

Đó là sức mạnh của sự lười biếng.


🚨 Những Cạm Bẫy Thường Gặp

  1. Sự cạn kiệt của iterator
Copy
it = iter([1,2,3])
print(list(it))  # [1,2,3]
print(list(it))  # [] (đã hết!)
  1. Chia sẻ một iterator qua các hàm
Copy
def f(it): return list(it)
def g(it): return list(it)

it = iter([1,2,3])
print(f(it))  # [1,2,3]
print(g(it))  # [] (oops)
  1. Vòng lặp vô hạn
Copy
import itertools
for x in itertools.count():
    print(x)   # sẽ không bao giờ dừng mà không có break

🎨 Mô Hình Tư Duy ASCII

Copy
[Iterator (người phục vụ)]
   |
   | next()
   V
 item → item → item → StopIteration

Người phục vụ đi về phía trước, bỏ đĩa. Khi không còn gì trong tay, trò chơi kết thúc.


🧭 Khi Nào Sử Dụng Iterators

  • ✅ Phát Streaming dữ liệu lớn (logs, CSVs, truy vấn DB).
  • ✅ Kết hợp lười biếng các pipeline (map, filter, itertools).
  • ✅ Dãy số vô hạn với điều kiện dừng được kiểm soát.
  • ❌ Không nên sử dụng nếu bạn cần truy cập ngẫu nhiên hoặc nhiều lần (sử dụng danh sách/tuple).

🎬 Kết Luận

Chúng ta đã học được:

  • Iterators là những người phục vụ có trạng thái: một chuyến đi, không quay lại.
  • Giao thức = __iter__() (trả về self) + __next__() (trả về món hoặc StopIteration).
  • Các iterator trong CPython là các đối tượng nhỏ, dựa trên con trỏ.
  • Generators chỉ là đường dẫn cú pháp để tạo ra các iterator.
  • Iterators tiết kiệm rất nhiều bộ nhớ nhưng có thể làm bạn gặp rắc rối với sự cạn kiệt.

👉 Phần tiếp theo (Phần 3): Mẹo Lặp Nâng Cao
Chúng ta sẽ khám phá itertools, yield from, phân quyền generator, phân nhánh một iterator, và cách xây dựng các pipeline lười biếng tùy chỉnh như một đầu bếp chuyên nghiệp thiết kế thực đơn.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào