0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Khám Phá Iterable Trong Python: Bàn Tiệc Buffet 🍽️

Đăng vào 7 tháng trước

• 6 phút đọc

Khám Phá Iterable Trong Python: Bàn Tiệc Buffet 🍽️

Khi bạn viết một vòng lặp đơn giản for x in something: trong Python, có rất nhiều điều đang diễn ra sau hậu trường. Cái "something" đó có thể là một danh sách, một chuỗi, một từ điển hoặc thậm chí là một luồng tệp tin. Nhưng điều gì thực sự làm cho một đối tượng có thể lặp lại được?

Câu trả lời: iterables.

Trong bài viết này (Phần 1 của chuỗi 3 phần), chúng ta sẽ khám phá các iterable với một phép ẩn dụ dễ hiểu: bàn tiệc buffet. Chúng ta sẽ tìm hiểu các lớp của giao thức lặp lại trong Python, xem xét chi tiết về bộ nhớ, những cạm bẫy và các trường hợp sử dụng trong cuộc sống thực. Đến cuối bài, bạn sẽ không bao giờ nhìn vào for item in data: theo cách giống như trước nữa.

🍴 Phép Ẩn Dụ Bàn Tiệc Buffet

Hãy tưởng tượng bạn bước vào một nhà hàng buffet. Cách bài trí trông như sau:

  • Bàn buffet = Iterable (container của các món ăn ở đây)
  • Đĩa = Iterator (trợ lý cá nhân của bạn theo dõi những gì bạn đã lấy)

Ý Tưởng Chính:

  • Bàn buffet không bao giờ "hết". Bạn luôn có thể lấy một cái đĩa mới và bắt đầu lại.
  • Mỗi cái đĩa biết vị trí của nó, vì vậy hai người có thể ăn từ cùng một bàn buffet một cách độc lập.

Iterable Là Gì Trong Python?

Một đối tượng được gọi là iterable nếu:

  1. Nó thực hiện __iter__() và trả về một iterator.
  2. Hoặc, nếu không, nó thực hiện __getitem__() với chỉ số bắt đầu từ 0, để Python có thể mô phỏng việc lặp lại bằng cách lấy obj[0], obj[1], … cho đến khi gặp IndexError.

Hãy nghĩ về iter(obj) như là chuông cửa phổ quát:

  • Nếu obj.__iter__ tồn tại, Python sẽ gọi nó.
  • Nếu không, nó sẽ thử phương thức dự phòng __getitem__.

Buffet Trong Code

python Copy
lst = [1, 2, 3]          # danh sách (iterable)
s = "hello"              # chuỗi (iterable)
st = {'x', 'y', 'z'}     # tập hợp (iterable, nhưng không có thứ tự)
d = {'a': 1, 'b': 2}     # từ điển (iterable qua các khóa)

# Giao thức lặp lại của Python dưới lớp vỏ
it = iter(lst)           # lấy một cái đĩa
print(next(it))          # 1
print(next(it))          # 2
print(next(it))          # 3

Lưu ý: tập hợp là iterable nhưng không thể chỉ mục (st[0]TypeError).

🧠 Chi Tiết Về Bộ Nhớ & Đối Tượng

  • Một danh sách là một mảng liên tục các con trỏ tới các đối tượng.
  • Một iterator (như list_iterator) là nhẹ:
    • Lưu trữ một tham chiếu tới danh sách gốc
    • Giữ một con trỏ nhỏ dạng số nguyên (vị trí tiếp theo)

👉 Iterators không sao chép dữ liệu. Chúng chỉ giữ một tham chiếu + vị trí → O(1) bộ nhớ overhead.

Ví dụ:

  • range(10_000_000) → lười biếng, không cấp phát 10 triệu số.
  • Iterators/generators cho phép bạn stream dữ liệu từng cái một với chi phí bộ nhớ gần như bằng 0.

Iterables Thông Qua Phương Thức Dự Phòng __getitem__

python Copy
class SquareSeq:
    def __getitem__(self, index):
        if index < 0:
            raise IndexError
        return index * index

sq = SquareSeq()
it = iter(sq)
print(next(it))  # 0
print(next(it))  # 1
print(next(it))  # 4

Tại đây, Python tiếp tục gọi sq[0], sq[1], … cho đến khi gặp IndexError.

⚠️ Cảnh báo: nếu bạn không raise IndexError, quá trình lặp lại có thể chạy mãi mãi.

Các Trường Hợp Sử Dụng Trong Thực Tế

  1. Xử lý các tệp log/CSV lớn → lặp qua từng dòng, không tải tất cả vào bộ nhớ.
  2. Gọi API phân trang → mỗi next() lấy một trang kết quả mới.
  3. Pipeline → kết hợp map, filter, itertools cho các luồng dữ liệu hiệu quả.
  4. Tính toán lười biếng → tính các giá trị tốn kém chỉ khi cần thiết.
  5. Khoảng số lớnrange() + cắt lát = lặp lại hiệu quả.

Cạm Bẫy & Điều Cần Lưu Ý

  • Iterators có thể bị cạn kiệt → một khi đã sử dụng, chúng không được làm đầy lại.
  • Tập hợp/từ điển không đảm bảo thứ tự (trừ khi bạn dựa vào thứ tự chèn của CPython).
  • Infinite iterables (itertools.count()) → phải cắt lát hoặc thêm điều kiện dừng.

Tạo Iterable Tùy Chỉnh Đúng Cách

Mẫu xấu (iterator = container):

python Copy
class BadCities:
    def __init__(self):
        self._cities = ["Paris", "Berlin", "Rome"]
        self._index = 0
    def __iter__(self): return self
    def __next__(self):
        if self._index >= len(self._cities): raise StopIteration
        val = self._cities[self._index]
        self._index += 1
        return val

Vấn đề: vòng lặp thứ hai không tìm thấy gì vì iterator đã cạn kiệt.

Mẫu tốt hơn (tách biệt container & iterator):

python Copy
class CityIterator:
    def __init__(self, cities):
        self._cities, self._index = cities, 0
    def __iter__(self): return self
    def __next__(self):
        if self._index >= len(self._cities): raise StopIteration
        val = self._cities[self._index]; self._index += 1
        return val

class Cities:
    def __init__(self): self._cities = ["Paris", "Berlin", "Rome"]
    def __iter__(self): return CityIterator(self._cities)

Bây giờ bạn có thể lặp qua Cities() nhiều lần.

Mô Hình Tư Duy ASCII

Copy
[Iterable (bàn buffet)]
       |
       | __iter__()  hoặc __getitem__()
       V
[Iterator (đĩa)] --> tham chiếu + con trỏ
       |
       | __next__()
       V
   các mục từng cái một
   StopIteration -> dừng

Danh Sách Kiểm Tra Nhanh ✅

  • Nhiều lần lặp lại? → sử dụng iterable, không phải iterator.
  • Tiết kiệm bộ nhớ? → sử dụng generator hoặc API dựa trên iterator.
  • Cần kiểm tra tính khả thi? → try: iter(obj) / except TypeError.
  • Điều kiện dừng đặc biệt? → iter(callable, sentinel).

Kết Luận

Trong phần 1, chúng ta đã học:

  • Iterable là gì (bàn buffet).
  • Cách hoạt động của iter() (__iter__ hoặc phương thức dự phòng __getitem__).
  • Tại sao iterator lại nhẹ.
  • Các trường hợp sử dụng iterable trong thực tế.
  • Cách thiết kế các iterable tùy chỉnh có thể tái sử dụng.

👉 Tiếp theo (Phần 2): Iterators là những người phục vụ với vé một chiều 🍽️. Chúng ta sẽ đi sâu vào __next__, StopIteration, và cách mà generators phù hợp vào bức tranh.

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