درک عمیق __iter__ و __next__ در پایتون

درک عمیق __iter__ و __next__ در پایتون

در پایتون، مفهوم تکرش‌پذیری (Iterability) یکی از ارکان اصلی کار با داده‌ها و ساختارهای مختلف است. حلقه‌های for، توابع map()، filter() و بسیاری از ابزارهای دیگر، بر پایه این مفهوم بنا شده‌اند. اما پشت پرده این قابلیت‌ها چه اتفاقی می‌افتد؟ در این مقاله، به بررسی عمیق دو متد ویژه __iter__() و __next__() می‌پردازیم که اساس پیاده‌سازی تکرش‌پذیری در پایتون را تشکیل می‌دهند. این مقاله در سطح ‘مفاهیم متوسط’ پایتون قرار می‌گیرد و برای کسانی که با مفاهیم پایه پایتون آشنا هستند، مناسب است.

تکرش‌پذیری چیست؟

یک شیء تکرش‌پذیر (Iterable) شیئی است که می‌توان بر روی عناصر آن پیمایش کرد. به عبارت دیگر، می‌توان از آن در یک حلقه for استفاده کرد. مثال‌های رایج از اشیاء تکرش‌پذیر شامل لیست‌ها، تاپل‌ها، دیکشنری‌ها، مجموعه‌ها و رشته‌ها هستند. اما تکرش‌پذیری یک ویژگی ذاتی نیست که به طور خودکار به همه اشیاء داده شود. بلکه باید با پیاده‌سازی متدهای خاصی، یک شیء را تکرش‌پذیر کرد.

نقش __iter__()

متد __iter__() مسئول بازگرداندن یک شیء تکرش‌کننده (Iterator) است. یک تکرش‌کننده شیئی است که مسئول تولید عناصر بعدی در یک دنباله است. هنگامی که یک شیء تکرش‌پذیر در یک حلقه for استفاده می‌شود، ابتدا متد __iter__() آن فراخوانی می‌شود تا یک تکرش‌کننده ایجاد شود. سپس، حلقه for به طور مکرر متد __next__() تکرش‌کننده را فراخوانی می‌کند تا عناصر بعدی را دریافت کند.

به طور خلاصه، __iter__() یک شیء تکرش‌کننده را برمی‌گرداند، در حالی که __next__() عنصر بعدی را تولید می‌کند.

نقش __next__()

متد __next__() مسئول بازگرداندن عنصر بعدی در یک دنباله است. این متد توسط حلقه for فراخوانی می‌شود تا عناصر بعدی را دریافت کند. هنگامی که هیچ عنصر دیگری برای بازگرداندن وجود نداشته باشد، متد __next__() باید یک استثنای StopIteration را ایجاد کند. این استثنا به حلقه for اطلاع می‌دهد که به انتهای دنباله رسیده است و باید خاتمه یابد.

پیاده‌سازی یک تکرش‌پذیر سفارشی

برای درک بهتر نحوه کارکرد __iter__() و __next__()، یک کلاس سفارشی ایجاد می‌کنیم که یک دنباله از اعداد را تولید می‌کند.

class MyNumbers:
    def __init__(self, max_value):
        self.current = 0
        self.max_value = max_value

    def __iter__(self):
        return self

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

در این مثال:

  • کلاس MyNumbers یک دنباله از اعداد را از 0 تا max_value تولید می‌کند.
  • متد __iter__() خود شیء MyNumbers را برمی‌گرداند. این بدان معناست که شیء MyNumbers هم تکرش‌پذیر و هم تکرش‌کننده است.
  • متد __next__() عنصر بعدی را بازمی‌گرداند. اگر current کمتر از max_value باشد، مقدار current را بازمی‌گرداند و current را یک واحد افزایش می‌دهد. در غیر این صورت، یک استثنای StopIteration را ایجاد می‌کند.

حالا می‌توانیم از این کلاس سفارشی در یک حلقه for استفاده کنیم:

numbers = MyNumbers(5)
for number in numbers:
    print(number)

خروجی این کد به شرح زیر خواهد بود:

0
1
2
3
4

تکرش‌پذیرها و تکرش‌کننده‌ها: یک تمایز مهم

همانطور که در مثال بالا دیدیم، یک شیء می‌تواند هم تکرش‌پذیر و هم تکرش‌کننده باشد. اما این همیشه ضروری نیست. در واقع، یک تکرش‌پذیر باید یک تکرش‌کننده را برگرداند، اما خود تکرش‌پذیر لزوماً نباید یک تکرش‌کننده باشد.

به عنوان مثال، لیست‌ها تکرش‌پذیر هستند، اما خود لیست‌ها تکرش‌کننده نیستند. هنگامی که یک لیست در یک حلقه for استفاده می‌شود، متد __iter__() لیست یک شیء تکرش‌کننده جدید ایجاد می‌کند که بر روی عناصر لیست پیمایش می‌کند.

این تمایز مهم است زیرا به ما امکان می‌دهد تکرش‌پذیری را به روشی انعطاف‌پذیر پیاده‌سازی کنیم. می‌توانیم یک کلاس ایجاد کنیم که فقط تکرش‌پذیر باشد و یک کلاس جداگانه ایجاد کنیم که مسئول تولید عناصر بعدی باشد.

ایجاد یک تکرش‌کننده جداگانه

برای نشان دادن این موضوع، یک کلاس تکرش‌کننده جداگانه برای لیست ایجاد می‌کنیم:

class MyListIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            value = self.data[self.index]
            self.index += 1
            return value
        else:
            raise StopIteration

و سپس یک کلاس تکرش‌پذیر که از این تکرش‌کننده استفاده می‌کند:

class MyList:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return MyListIterator(self.data)

حالا می‌توانیم از این کلاس‌ها به صورت زیر استفاده کنیم:

my_list = MyList([10, 20, 30])
for item in my_list:
    print(item)

خروجی این کد به شرح زیر خواهد بود:

10
20
30

کاربردهای پیشرفته

__iter__() و __next__() کاربردهای پیشرفته‌ای نیز دارند. به عنوان مثال، می‌توان از آن‌ها برای ایجاد:

  • تولیدکننده‌ها (Generators): تولیدکننده‌ها توابعی هستند که از کلمه کلیدی yield برای بازگرداندن عناصر بعدی استفاده می‌کنند. تولیدکننده‌ها به طور خودکار متدهای __iter__() و __next__() را پیاده‌سازی می‌کنند.
  • دنباله‌های بی‌نهایت: می‌توان از __iter__() و __next__() برای ایجاد دنباله‌هایی که هرگز به پایان نمی‌رسند، مانند دنباله اعداد فیبوناچی.
  • پردازش داده‌های بزرگ: با استفاده از تکرش‌پذیری، می‌توان داده‌های بزرگ را به صورت تدریجی پردازش کرد، بدون اینکه نیاز به بارگذاری کل داده‌ها در حافظه باشد.

نتیجه‌گیری

درک متدهای __iter__() و __next__() برای تسلط بر مفهوم تکرش‌پذیری در پایتون ضروری است. با پیاده‌سازی این متدها، می‌توان اشیاء سفارشی ایجاد کرد که می‌توانند در حلقه‌های for و سایر ابزارهای تکرش‌پذیر استفاده شوند. این قابلیت به ما امکان می‌دهد کدهای انعطاف‌پذیر و کارآمدتری بنویسیم و با داده‌ها به روشی مؤثرتر کار کنیم.

بدون دیدگاه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *