Cơ chế hoạt động của iter, reversed và next trong Python

Trong Python, giao thức lặp (iterator protocol) và việc đảo ngược đối tượng được thực thi thông qua các phương thức đặc biệt (magic methods). Việc hiểu rõ cách vận hành của các phương thức này giúp chúng ta tùy biến hành vi của các lớp (class) khi làm việc với các cấu trúc vòng lặp. Dưới đây là ba phương thức cốt lõi: 1. `__iter__(self)`: Phương thức này được gọi khi bạn cần khởi tạo một trình lặp (iterator) cho một container. Nó phải trả về chính đối tượng iterator đó. Thông thường, iterator chính là chính nó (self). 2. `__next__(self)`: Phương thức này chịu trách nhiệm trả về phần tử tiếp theo từ container. Khi không còn phần tử nào để trả về, nó phải ném ra ngoại lệ `StopIteration`. 3. `__reversed__(self)`: Được gọi bởi hàm tích hợp `reversed()`. Nó trả về một trình lặp mới để duyệt qua các phần tử của container theo thứ tự ngược lại.

Sử dụng iter và next để tạo trình lặp tùy chỉnh

Để một đối tượng có thể sử dụng trong vòng lặp `for`, nó cần triển khai cả `__iter__` và `__next__`.
class StepCounter:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        # Khởi tạo lại trạng thái khi bắt đầu vòng lặp
        self.current = 0
        return self

    def __next__(self):
        if self.current < self.limit:
            self.current += 1
            return self.current
        else:
            raise StopIteration

# Sử dụng
counter = StepCounter(5)
for num in counter:
    print(num)
Kết quả đầu ra:
1
2
3
4
5

Tìm hiểu về reversed và cơ chế của vòng lặp for

Khi sử dụng hàm `reversed()`, Python sẽ tìm kiếm phương thức `__reversed__`. Tuy nhiên, để kết quả của `reversed()` có thể dùng được trong vòng lặp `for`, đối tượng trả về phải là một iterator (tức là phải có `__iter__`).
class Countdown:
    def __init__(self, start_val):
        self.start_val = start_val
        self.index = 0

    def __reversed__(self):
        self.index = self.start_val
        return self

    def __next__(self):
        if self.index > 0:
            val = self.index
            self.index -= 1
            return val
        else:
            raise StopIteration

# Khởi tạo đối tượng
cd = Countdown(3)
rev_iter = reversed(cd)

# Gọi trực tiếp next() hoạt động bình thường
print(next(rev_iter))
print(next(rev_iter))

# Đoạn mã dưới đây sẽ báo lỗi: TypeError: 'Countdown' object is not iterable
# Vì class Countdown chưa định nghĩa __iter__
# for i in rev_iter:
#     print(i)
Lý do lỗi: Vòng lặp `for` trong Python luôn gọi hàm `iter()` lên đối tượng mà nó nhận được. Nếu `__reversed__` trả về `self` nhưng `self` không có phương thức `__iter__`, vòng lặp sẽ thất bại dù `__next__` đã được định nghĩa.

Kết hợp linh hoạt iter, reversed và next

Chúng ta có thể sử dụng một biến trạng thái (flag) để điều khiển hướng duyệt dữ liệu bên trong cùng một phương thức `__next__`.
class DataStream:
    def __init__(self, size):
        self.size = size
        self.cursor = 0
        self.is_forward = True

    def __iter__(self):
        self.cursor = 0
        self.is_forward = True
        return self

    def __reversed__(self):
        self.cursor = 0
        self.is_forward = False
        return self

    def __next__(self):
        if self.cursor >= self.size:
            raise StopIteration
        
        # Logic tính toán giá trị dựa trên hướng duyệt
        if self.is_forward:
            value = self.cursor + 1
        else:
            value = self.size - self.cursor
            
        self.cursor += 1
        return value

stream = DataStream(3)

print("--- Duyệt xuôi với iter ---")
for x in stream:
    print(x)

print("--- Duyệt ngược với reversed ---")
# Lưu ý: reversed(stream) trả về chính stream nhưng đổi flag is_forward
# Để dùng được trong for, stream phải có __iter__
rev_stream = reversed(stream)
for x in rev_stream:
    print(x)
Kết quả vận hành:
--- Duyệt xuôi với iter ---
1
2
3
--- Duyệt ngược với reversed ---
3
2
1
Trong ví dụ cuối cùng, khi gọi `for x in reversed(stream)`, trình tự thực thi sẽ là: 1. `reversed(stream)` được gọi, kích hoạt `__reversed__`, đặt `is_forward = False` và trả về đối tượng `stream`. 2. Vòng lặp `for` gọi `iter()` lên đối tượng vừa nhận được. Nếu `__iter__` reset lại `is_forward = True`, logic duyệt ngược sẽ bị ghi đè. Do đó, trong các ứng dụng thực tế, người ta thường tách biệt iterator xuôi và ngược thành các lớp khác nhau hoặc xử lý logic `__iter__` cẩn thận hơn để tránh xung đột trạng thái.

Thẻ: python Iterator magic-methods object-oriented-programming

Đăng vào ngày 6 tháng 8 lúc 16:56