ایتراتورها و ایتریبل‌ها در پایتون

تکرار کننده ها و تکرارشونده ها در پایتون

در این مقاله چه میخوانیم؟

مقدمه

آیا تا به حال به این فکر کرده‌اید که پایتون چگونه می‌تواند به‌صورت کارآمد و بهینه روی داده‌های مختلف حلقه بزند؟ یکی از مفاهیم کلیدی در این زبان که اجرای این وظایف را امکان‌پذیر می‌کند، ایتراتورها و ایتریبل‌ها در پایتون (Iterators and Iterables in Python) هستند.
این دو مفهوم پایه‌ای، زیرساخت اصلی تکرار در پایتون را تشکیل می‌دهند و تقریباً در تمام بخش‌های کدنویسی، از کار با لیست‌ها گرفته تا پردازش داده‌های پیچیده، حضور دارند.

 

تکرار کننده ها و تکرار شونده ها


ایتراتورها به شما امکان می‌دهند تا به جای بارگذاری تمام داده‌ها در حافظه، تنها با یک عنصر در هر لحظه کار کنید. از طرف دیگر، ایتریبل‌ها مجموعه‌هایی هستند که می‌توان از طریق آن‌ها تکرار انجام داد. این ترکیب، ابزار قدرتمندی برای مدیریت داده‌ها و اجرای تکرارهای بهینه فراهم می‌کند.
در این مقاله، به دنیای ایتراتورها و ایتریبل‌ها سفر می‌کنیم، تفاوت‌ها و کاربردهای آن‌ها را بررسی می‌کنیم و یاد می‌گیریم چگونه می‌توانیم از این مفاهیم برای افزایش کارایی کدهای خود استفاده کنیم. اگر به دنبال بهینه‌سازی حافظه، نوشتن کدهای تمیزتر و تسلط بر مدیریت تکرارها هستید، این راهنما برای شماست. 😊

 

درک مفهوم تکرار در پایتون

تکرار (Iteration) یکی از مهم‌ترین مفاهیم پایه در زبان برنامه‌نویسی پایتون است. این ویژگی به شما اجازه می‌دهد به‌سادگی و با کمترین پیچیدگی، روی مجموعه‌های مختلف داده مانند لیست‌ها، تاپل‌ها، رشته‌ها یا حتی فایل‌ها حرکت کنید و عناصر آن‌ها را بررسی یا پردازش کنید. در پایتون، تکرار به لطف دو مفهوم قدرتمند به نام ایتراتورها و ایتریبل‌ها (Iterators and Iterables in Python) امکان‌پذیر شده است.

 

درک مفهوم تکرار در پایتون

 

معرفی مفاهیم پایه تکرار در پایتون

 

برای درک بهتر تکرار، باید با دو مفهوم کلیدی آشنا شوید:

 

  • ایتریبل‌ها (Iterables): هر شی‌ای که شامل مجموعه‌ای از داده‌هاست و می‌توان روی آن تکرار انجام داد. مثال‌هایی از ایتریبل‌ها شامل لیست‌ها، تاپل‌ها و رشته‌ها هستند.
  • ایتراتورها (Iterators): ابزاری که وظیفه دارد عناصر یک ایتریبل را یکی‌یکی بازگرداند. ایتراتورها کمک می‌کنند تا حافظه بهینه‌تر استفاده شود و فقط داده‌ای که نیاز است، در هر لحظه پردازش شود.

 

در پایتون، حلقه‌های for از این مفاهیم استفاده می‌کنند تا به‌سادگی و بدون نیاز به نوشتن کدهای پیچیده، عملیات تکرار روی داده‌ها را انجام دهند.

 

ایتراتورها در پایتون | تعریف، کاربردها و نحوه عملکرد

ایتراتورها (Iterators) یکی از مفاهیم کلیدی در پایتون هستند که امکان تکرار بهینه روی داده‌ها را فراهم می‌کنند. این ابزارها به شما اجازه می‌دهند تا به‌صورت مرحله‌به‌مرحله عناصر یک مجموعه را بدون نیاز به بارگذاری کل داده‌ها در حافظه پردازش کنید. ایتراتورها یکی از اجزای اصلی پشت پرده حلقه‌های for هستند و در کنار ایتریبل‌ها (Iterables) نقش مهمی در مدیریت تکرار ایفا می‌کنند.

 

ایتراتور در پایتون چیست؟

یک ایتراتور در پایتون شی‌ای است که از متدهای __iter__() و __next__() پشتیبانی می‌کند. این متدها اجازه می‌دهند که ایتراتور به ترتیب روی عناصر یک مجموعه حرکت کرده و هر بار یک عنصر را بازگرداند. وقتی همه عناصر پردازش شدند، ایتراتور یک استثنا به نام StopIteration ایجاد می‌کند تا نشان دهد که دیگر داده‌ای برای پردازش باقی نمانده است. به بیان ساده، ایتراتور ابزاری است که به‌جای بازگرداندن تمام داده‌ها به یکباره، داده‌ها را به‌صورت مرحله‌ای بازمی‌گرداند.

 

آشنایی با ایتراتورهای پایتون

 

پروتکل ایتراتور در پایتون چیست؟

پروتکل ایتراتور در پایتون شامل مجموعه‌ای از قوانین است که یک شی باید برای تبدیل شدن به ایتراتور رعایت کند:

 

  1. شیء باید متد __iter__() را داشته باشد که خودش را بازگرداند.
  2. متد __next__() باید وجود داشته باشد که مقدار بعدی در توالی را بازگرداند. اگر داده‌ای باقی نمانده باشد، این متد باید استثنای StopIteration را ایجاد کند.

 

این پروتکل به پایتون امکان می‌دهد تا روی مجموعه‌های مختلف به‌صورت استاندارد و بدون نیاز به پیاده‌سازی کد اضافی تکرار کند. برای مثال، لیست‌ها و رشته‌ها در پایتون به‌طور پیش‌فرض از این پروتکل پشتیبانی می‌کنند.

 

چه زمانی باید از ایتراتور در پایتون استفاده کرد؟

استفاده از ایتراتورها زمانی کاربرد دارد که بخواهید:

 

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

 

برای مثال، اگر بخواهید خطوط یک فایل بسیار بزرگ را بخوانید، استفاده از یک ایتراتور به جای بارگذاری کل فایل به شما کمک می‌کند تا حافظه کمتری مصرف کنید:

 

with open("large_file.txt", "r") as file:
    for line in file:
        print(line.strip())

 

ایجاد انواع مختلف ایتراتورها

یکی از ویژگی‌های قدرتمند پایتون، توانایی ایجاد ایتراتورهای سفارشی برای پاسخگویی به نیازهای خاص برنامه‌نویسی است. ایتراتورها (Iterators) به شما این امکان را می‌دهند که داده‌ها را با انعطاف بیشتری پردازش کنید و انواع مختلفی از خروجی‌ها را بر اساس نیاز خود ایجاد کنید. در این بخش، روش‌های متنوعی برای ایجاد ایتراتورها را بررسی می‌کنیم.

 

ایجاد انواع مختلف ایتراتورها

 

بازگرداندن داده‌های اصلی

یکی از ساده‌ترین انواع ایتراتورها، ایتراتوری است که داده‌های موجود را بدون تغییر بازمی‌گرداند. این روش برای مواقعی که فقط نیاز به پردازش هر عنصر از یک مجموعه به ترتیب دارید، بسیار مناسب است. مثلاً اگر بخواهید عناصر یک لیست را یکی‌یکی بازگردانید، می‌توانید از این نوع ایتراتور استفاده کنید:

 

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

    def __iter__(self):
        return self

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

 

تبدیل داده‌های ورودی

گاهی ممکن است نیاز داشته باشید که داده‌های ورودی را قبل از بازگرداندن، تغییر دهید. به‌عنوان مثال، فرض کنید می‌خواهید عناصر یک لیست از رشته‌ها را به حروف بزرگ تبدیل کنید. در چنین حالتی، می‌توانید یک ایتراتور برای تبدیل داده‌ها بنویسید:

 

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

    def __iter__(self):
        return self

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

 

تولید داده‌های جدید

ایتراتورها همچنین می‌توانند داده‌های جدید تولید کنند. این قابلیت برای محاسبات پویا یا تولید مجموعه‌های داده‌ای که به‌صورت پیش‌فرض وجود ندارند، بسیار کاربردی است. به‌عنوان مثال، می‌توانید یک ایتراتور بنویسید که اعداد مربع را تولید کند:

 

class SquareIterator:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= self.end:
            result = self.current ** 2
            self.current += 1
            return result
        else:
            raise StopIteration

 

کدنویسی ایتراتورهای بالقوه بی‌نهایت

ایتراتورهای بی‌نهایت برای تولید داده‌هایی که اندازه یا طول مشخصی ندارند، بسیار مفیدند. برای مثال، تولید دنباله فیبوناچی می‌تواند از این نوع ایتراتورها استفاده کند:

 

class FibonacciIterator:
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        return result

 

این ایتراتور می‌تواند دنباله بی‌نهایت فیبوناچی تولید کند، اما باید با یک شرط مانند حلقه for تعداد محدودی از مقادیر را از آن دریافت کنید.

 

ارث‌بری از collections.abc.Iterator

 

برای پیاده‌سازی ایتراتورهای پیشرفته‌تر و استانداردسازی کد، می‌توانید از کلاس انتزاعی collections.abc.Iterator استفاده کنید. این روش تضمین می‌کند که ایتراتور شما از تمام الزامات پروتکل ایتراتور پایتون پیروی می‌کند:

 

 

from collections.abc import Iterator

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

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


 

 

ایجاد ایتراتورهای مولد (Generator Iterators)

 

مولدها (Generators) یکی از ساده‌ترین و قدرتمندترین ابزارها در پایتون برای ایجاد ایتراتورها هستند. برخلاف ایتراتورهای دستی که نیاز به پیاده‌سازی متدهای __iter__ و __next__ دارند، جنریتورها از کلمات کلیدی مثل yield استفاده می‌کنند تا فرآیند تولید داده‌ها را ساده‌تر کنند.
جنریتورها نه تنها کدنویسی را آسان‌تر می‌کنند، بلکه حافظه را بهینه‌تر مصرف می‌کنند، زیرا فقط در لحظه نیاز، داده تولید می‌کنند.

 

یجاد ایتراتورهای مولد (Generators)

 

ایجاد توابع مولد (Generator Functions)

 

توابع مولد، توابع خاصی هستند که به جای استفاده از return، از کلمه کلیدی yield برای بازگرداندن داده‌ها استفاده می‌کنند. این توابع به‌صورت خودکار یک ایتراتور ایجاد می‌کنند و هر بار که تابع فراخوانی می‌شود، اجرای آن از جایی که قبلاً متوقف شده بود ادامه می‌یابد.

 

 

def square_numbers(start, end):
    for num in range(start, end + 1):
        yield num ** 2

# استفاده از تابع مولد
for square in square_numbers(1, 5):
    print(square)

 

در این مثال، تابع square_numbers اعداد مربع را در بازه‌ای مشخص تولید می‌کند و هر بار که از آن استفاده می‌کنید، یک مقدار جدید بازمی‌گرداند.

 

استفاده از عبارت‌های مولد برای ایجاد ایتراتورها

پایتون امکان استفاده از عبارت‌های مولد (Generator Expressions) را فراهم می‌کند که شبیه به لیست کامپرهنشن‌ها هستند، اما به جای ایجاد لیست در حافظه، یک مولد ایجاد می‌کنند. این روش برای داده‌های بزرگ یا زمانی که نیازی به نگهداری کل داده‌ها نیست، بسیار مفید است.

 

 

squares = (num ** 2 for num in range(1, 6))
print(next(squares))  # خروجی: ۱
print(next(squares))  # خروجی: ۴

 

در اینجا، عبارت (num ** 2 for num in range(1, 6)) یک مولد ایجاد می‌کند که اعداد مربع را یکی‌یکی تولید می‌کند.

 

بررسی انواع مختلف ایتراتورهای مولد

جنریتورها می‌توانند برای اهداف مختلفی طراحی شوند، از ساده‌ترین وظایف تا وظایف پیچیده‌تر. در ادامه چند نوع از جنریتورهای متداول آورده شده است:

۱.مولدهای شمارشی: تولید یک دنباله از اعداد یا رشته‌ها:

 

def countdown(n):
    while n > 0:
        yield n
        n -= 1

 

۲.مولدهای تصادفی: تولید داده‌های تصادفی:

 

import random

def random_numbers(count, start, end):
    for _ in range(count):
        yield random.randint(start, end)

 

 

۳.مولدهای بی‌نهایت: تولید داده بدون محدودیت (تا زمانی که حلقه متوقف شود):

 

def infinite_fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

 

 

پردازش داده‌ها با استفاده بهینه از حافظه با ایتراتورها

ایتراتورها (Iterators) یکی از بهترین ابزارها برای پردازش داده‌ها در پایتون هستند، به‌ویژه زمانی که نیاز به مدیریت حجم زیادی از داده‌ها دارید. برخلاف کانتینرهای سنتی مثل لیست‌ها، ایتراتورها داده‌ها را به‌صورت مرحله‌به‌مرحله پردازش می‌کنند و از بارگذاری کل داده‌ها در حافظه جلوگیری می‌کنند. این ویژگی به شما امکان می‌دهد تا کدهای کارآمدتر و بهینه‌تری بنویسید.

 

پردازش داده‌ها با استفاده بهینه از حافظه با ایتراتورها

 

بازگرداندن ایتراتورها به‌جای انواع کانتینر

در بسیاری از مواقع، به جای بازگرداندن یک کانتینر مثل لیست یا دیکشنری، می‌توانید از ایتراتورها استفاده کنید. بازگرداندن ایتراتورها نه تنها باعث کاهش مصرف حافظه می‌شود، بلکه به شما امکان می‌دهد تا داده‌ها را در لحظه تولید و پردازش کنید.

 

def generate_numbers(n):
    for i in range(n):
        yield i

# استفاده از ایتراتور به جای لیست
for number in generate_numbers(5):
    print(number)

 

در این مثال، به‌جای ساختن یک لیست از اعداد، ایتراتور اعداد را به صورت پویا تولید می‌کند. این روش به‌ویژه در مواقعی که داده‌ها بسیار بزرگ هستند، بسیار مفید است.

 

ایجاد یک خط لوله پردازش داده با ایتراتورهای مولد

یکی از ویژگی‌های منحصربه‌فرد ایتراتورها، امکان زنجیره‌سازی آن‌ها برای ایجاد خطوط لوله پردازش داده است. در این روش، هر ایتراتور به‌عنوان یک مرحله در خط لوله عمل می‌کند و داده‌ها را به‌صورت مرحله‌ای پردازش می‌کند.

 

def read_file(file_path):
    with open(file_path, "r") as file:
        for line in file:
            yield line.strip()

def filter_lines(lines, keyword):
    for line in lines:
        if keyword in line:
            yield line

def count_lines(lines):
    return sum(1 for _ in lines)

# استفاده از خط لوله پردازش داده
file_lines = read_file("example.txt")
filtered_lines = filter_lines(file_lines, "Python")
result = count_lines(filtered_lines)
print(f"Number of lines containing 'Python': {result}")

 

در این مثال:

  1. read_file خطوط فایل را به صورت پویا می‌خواند.
  2. filter_lines خطوطی را که حاوی کلمه کلیدی خاصی هستند، فیلتر می‌کند.
  3. count_lines تعداد خطوط فیلترشده را محاسبه می‌کند.

 

این روش بهینه‌ترین استفاده از حافظه را تضمین می‌کند، زیرا هیچ‌گاه کل داده‌ها به‌صورت همزمان در حافظه بارگذاری نمی‌شوند.

درک برخی محدودیت‌های ایتراتورهای پایتون

ایتراتورها (Iterators) ابزارهای قدرتمندی برای مدیریت و پردازش داده‌ها در پایتون هستند، اما مانند هر ابزار دیگری، محدودیت‌ها و چالش‌های خاص خود را دارند. آگاهی از این محدودیت‌ها به شما کمک می‌کند تا از ایتراتورها به شکلی موثرتر و بدون بروز مشکلات غیرمنتظره استفاده کنید.

 

درک محدودیت‌های ایتراتورها

 

محدودیت‌ها و مسائل رایج در ایتراتورها

۱.مصرف‌پذیری یک‌بار (One-Time Consumption):


ایتراتورها تنها یک بار قابل استفاده هستند. وقتی یک ایتراتور ایجاد می‌کنید و تمام داده‌های آن را مصرف می‌کنید، نمی‌توانید دوباره به ابتدای آن بازگردید. اگر بخواهید از داده‌های ایتراتور دوباره استفاده کنید، باید یک ایتراتور جدید ایجاد کنید

 

iterator = iter([1, 2, 3])
for num in iterator:
    print(num)
# تلاش برای استفاده مجدد از ایتراتور
for num in iterator:
    print(num)  # هیچ خروجی نخواهد داشت

 

۲.عدم پشتیبانی از طول (No Length Support):

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

 

۳.عدم دسترسی تصادفی (No Random Access):

 

ایتراتورها فقط امکان دسترسی ترتیبی به داده‌ها را فراهم می‌کنند. نمی‌توانید به‌طور مستقیم به یک عنصر خاص دسترسی پیدا کنید، زیرا ایتراتورها بر اساس جریان داده عمل می‌کنند و مفهوم “اندیس‌دهی” برای آن‌ها وجود ندارد.

 

۴.مدیریت خطای StopIteration:

اگر ایتراتور به پایان داده‌ها برسد، استثنای StopIteration را ایجاد می‌کند. مدیریت این خطا در کدهای پیچیده می‌تواند دشوار باشد، به‌خصوص اگر از چندین ایتراتور به‌صورت هم‌زمان استفاده کنید.

 

۵.کاهش خوانایی در موارد پیچیده:

هرچند ایتراتورها در موارد ساده بسیار مفید هستند، اما استفاده نادرست از آن‌ها در پروژه‌های پیچیده می‌تواند کد را دشوارتر برای خواندن و نگهداری کند.

 

۶.عملکرد کند در برخی موارد:

در مواردی که نیاز به دسترسی‌های متعدد و سریع به داده‌ها دارید، ایتراتورها ممکن است کندتر از کانتینرهایی مانند لیست‌ها عمل کنند، زیرا مجبورند داده‌ها را مرحله به مرحله پردازش کنند.

 

استفاده از تابع داخلی next()

 

تابع داخلی next() یکی از ابزارهای کلیدی در پایتون برای کار با ایتراتورها (Iterators) است. این تابع به شما اجازه می‌دهد تا مستقیماً به عنصر بعدی در یک ایتراتور دسترسی پیدا کنید.
استفاده از next() برای موقعیت‌هایی مناسب است که نیاز دارید فرآیند تکرار را به‌صورت دستی کنترل کنید، به جای اینکه از حلقه‌های خودکار مانند for استفاده کنید.

 

استفاده از تابع next()

 

توضیح و مثال‌هایی از تابع next() در پایتون

تابع next() برای دریافت عنصر بعدی یک ایتراتور استفاده می‌شود. این تابع دو ویژگی مهم دارد:

 

  1. با هر فراخوانی، مقدار بعدی ایتراتور را بازمی‌گرداند.
  2. اگر ایتراتور به پایان برسد، یک استثنا به نام StopIteration ایجاد می‌کند.

 

مثال ساده:

 

iterator = iter([1, 2, 3])
print(next(iterator))  # خروجی: ۱
print(next(iterator))  # خروجی: ۲
print(next(iterator))  # خروجی: ۳
# اگر دوباره فراخوانی شود، خطای StopIteration ایجاد می‌کند:
# print(next(iterator))

 

مدیریت خطای StopIteration

 

برای جلوگیری از خطای StopIteration می‌توانید از یک مقدار پیش‌فرض استفاده کنید. این مقدار به‌عنوان جایگزین بازگردانده می‌شود اگر ایتراتور به انتها رسیده باشد:

 

iterator = iter([1, 2, 3])
print(next(iterator, "پایان"))  # خروجی: ۱
print(next(iterator, "پایان"))  # خروجی: ۲
print(next(iterator, "پایان"))  # خروجی: ۳
print(next(iterator, "پایان"))  # خروجی: پایان

 

 

کنترل دستی فرآیند تکرار

 

یکی از کاربردهای next() کنترل دستی فرآیند تکرار در شرایط خاص است. این تابع می‌تواند در ترکیب با حلقه‌ها یا شرط‌ها استفاده شود:

 

iterator = iter([10, 20, 30])
while True:
    try:
        item = next(iterator)
        print(f"عنصر بعدی: {item}")
    except StopIteration:
        print("تمام عناصر پردازش شدند.")
        break


 

 

آشنایی با ایتریبل‌های پایتون

در پایتون، ایتریبل‌ها (Iterables) اشیایی هستند که می‌توان روی آن‌ها تکرار (Iteration) انجام داد. این اشیا نقش مهمی در حلقه‌ها و عملیات تکراری دارند و پایه‌ای برای استفاده از ایتراتورها (Iterators) محسوب می‌شوند. فهم ایتریبل‌ها و نحوه عملکرد آن‌ها در کنار ایتراتورها، به شما کمک می‌کند تا کدهایی کارآمدتر و تمیزتر بنویسید.

 

آشنایی با ایتریبل‌ها

 

پروتکل ایتریبل (Iterable Protocol)

پروتکل ایتریبل مجموعه‌ای از قوانین است که یک شیء باید برای تبدیل شدن به یک ایتریبل رعایت کند. برای این‌که یک شیء در پایتون به‌عنوان ایتریبل شناخته شود، باید متد __iter__() را پیاده‌سازی کند. این متد باید یک ایتراتور بازگرداند. تمام ساختارهای داده مانند لیست‌ها، تاپل‌ها، و دیکشنری‌ها به‌طور پیش‌فرض از این پروتکل پشتیبانی می‌کنند.

 

my_list = [1, 2, 3]
iterator = iter(my_list)
print(next(iterator))  # خروجی: ۱


تابع داخلی iter()

پایتون تابع داخلی iter() را برای دریافت ایتراتور از یک ایتریبل ارائه می‌دهد. این تابع متد __iter__() را در پس‌زمینه فراخوانی می‌کند و ایتراتور مربوطه را بازمی‌گرداند.

 

my_tuple = (10, 20, 30)
iterator = iter(my_tuple)

print(next(iterator))  # خروجی: ۱۰
print(next(iterator))  # خروجی: ۲۰

 

این تابع ابزاری ساده و کاربردی برای تبدیل هر ایتریبل به یک ایتراتور است.

 

تابع داخلی reversed()

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

 

numbers = [1, 2, 3]
for num in reversed(numbers):
    print(num)
# خروجی:
# ۳
# ۲
# ۱

 

اگر شیء قابلیت معکوس شدن نداشته باشد، این تابع خطای TypeError ایجاد می‌کند.

 

پروتکل Sequence

پروتکل Sequence یکی از ویژگی‌های مهم ایتریبل‌هاست که امکان دسترسی به داده‌ها را از طریق اندیس‌دهی فراهم می‌کند. لیست‌ها، رشته‌ها و تاپل‌ها از این پروتکل پیروی می‌کنند و می‌توانید از اندیس‌دهی و برش (Slicing) برای دسترسی به داده‌ها استفاده کنید.

 

my_string = "Python"
print(my_string[0])  # خروجی: P
print(my_string[1:4])  # خروجی: yth

 

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

 

کار با ایتریبل‌ها در پایتون

ایتریبل‌ها (Iterables) در پایتون به شما امکان می‌دهند تا به شکلی ساده و کارآمد روی مجموعه‌های داده حرکت کنید. پایتون ابزارها و قابلیت‌های قدرتمندی برای کار با ایتریبل‌ها فراهم می‌کند که از حلقه‌های for گرفته تا کامپرهنشن‌ها و عملیات باز کردن (Unpacking) را شامل می‌شود.
در این بخش، روش‌های مختلف استفاده از ایتریبل‌ها را بررسی می‌کنیم.

 

کار با ایتریبل‌ها در پایتون

 

تکرار از طریق ایتریبل‌ها با حلقه‌های For

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

 

numbers = [1, 2, 3, 4]
for number in numbers:
    print(number)
# خروجی:
# ۱
# ۲
# ۳
# ۴

 

تکرار از طریق Comprehensions

پایتون با ارائه Comprehensions امکان ایجاد و پردازش ایتریبل‌ها را به‌صورت فشرده و کارآمد فراهم می‌کند. این قابلیت می‌تواند برای لیست‌ها، مجموعه‌ها و دیکشنری‌ها استفاده شود و کدنویسی را کوتاه‌تر و خواناتر کند.

 

# لیست کامپرهنشن
squares = [x ** 2 for x in range(5)]
print(squares)  # خروجی: [0, 1, 4, 9, 16]

# مجموعه کامپرهنشن
unique_squares = {x ** 2 for x in range(5)}
print(unique_squares)  # خروجی: {۰, ۱, ۴, ۹, ۱۶}

# دیکشنری کامپرهنشن
square_dict = {x: x ** 2 for x in range(5)}
print(square_dict)  # خروجی: {۰: ۰, ۱: ۱, ۲: ۴, ۳: ۹, ۴: ۱۶}

 

کامپرهنشن‌ها برای پردازش داده‌ها و ایجاد ایتریبل‌های جدید در یک خط کد بسیار مفید هستند.

 

باز کردن (Unpacking) ایتریبل‌ها

پایتون به شما اجازه می‌دهد که ایتریبل‌ها را به‌صورت مستقیم باز کنید (Unpack) و مقادیر آن‌ها را به متغیرهای مختلف اختصاص دهید. این قابلیت برای کار با داده‌های پیچیده‌تر و خواندن سریع مقادیر بسیار کاربردی است.

 

# باز کردن لیست
a, b, c = [10, 20, 30]
print(a, b, c)  # خروجی: ۱۰ ۲۰ ۳۰

# استفاده از * برای باز کردن بقیه عناصر
first, *middle, last = [1, 2, 3, 4, 5]
print(first)  # خروجی: ۱
print(middle)  # خروجی: [2, 3, 4]
print(last)  # خروجی: ۵

 

بررسی روش‌های جایگزین برای نوشتن .__iter__() در ایتریبل‌ها

در پایتون، هر شیء ایتریبل (Iterable) باید متد .__iter__() را پیاده‌سازی کند. این متد وظیفه دارد ایتراتور مربوط به شیء را بازگرداند. اگرچه پیاده‌سازی مستقیم این متد می‌تواند نیازهای پایه را برطرف کند، روش‌های جایگزین و پیشرفته‌تری نیز وجود دارند که کدنویسی را ساده‌تر و عملکرد را بهینه‌تر می‌کنند.
در این بخش، به بررسی این روش‌ها و بهترین تکنیک‌ها برای پیاده‌سازی .__iter__() می‌پردازیم.

 

بررسی روش‌های جایگزین برای .iter()

 

تکنیک‌ها و بهترین روش‌ها برای پیاده‌سازی .__iter__()

استفاده مستقیم از Generators: ساده‌ترین روش برای پیاده‌سازی .__iter__() استفاده از جنریتورها (Generators) است. با استفاده از کلمه کلیدی yield می‌توانید ایتراتور مربوطه را بدون نیاز به پیاده‌سازی دستی __next__() تولید کنید.

 

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

    def __iter__(self):
        for item in self.data:
            yield item

my_iterable = CustomIterable([1, 2, 3])
for item in my_iterable:
    print(item)
# خروجی: ۱, ۲, ۳

 

ارث‌بری از کلاس‌های استاندارد پایتون:

برای ساده‌تر کردن پیاده‌سازی، می‌توانید از کلاس‌های آماده موجود در collections.abc استفاده کنید. این کلاس‌ها مانند Iterable و Iterator پروتکل‌های موردنیاز را از پیش تعریف کرده‌اند.

 

from collections.abc import Iterable

class CustomIterable(Iterable):
    def __init__(self, data):
        self.data = data

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

my_iterable = CustomIterable([4, 5, 6])
for item in my_iterable:
    print(item)
# خروجی: ۴, ۵, ۶

 

استفاده از متد داخلی iter()

: اگر شیء شما شامل داده‌های قابل تکرار مانند لیست یا تاپل است، می‌توانید مستقیماً از تابع داخلی iter() در پیاده‌سازی .__iter__() استفاده کنید.

 

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

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

my_iterable = CustomIterable(["a", "b", "c"])
for char in my_iterable:
    print(char)
# خروجی: a, b, c


ایتراتور سفارشی با متد .__iter__() و .__next__()

اگر نیاز به کنترل دقیق‌تر بر فرآیند تکرار دارید، می‌توانید یک کلاس جداگانه برای ایتراتور ایجاد کرده و متدهای __iter__() و __next__() را پیاده‌سازی کنید.

 

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

    def __iter__(self):
        return self

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

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

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

my_iterable = CustomIterable([10, 20, 30])
for num in my_iterable:
    print(num)
# خروجی: ۱۰, ۲۰, ۳۰


مقایسه ایتراتورها و ایتریبل‌ها

ایتراتورها (Iterators) و ایتریبل‌ها (Iterables) دو مفهوم مرتبط اما متفاوت در پایتون هستند که در کنار یکدیگر، امکان تکرار کارآمد روی مجموعه‌های داده را فراهم می‌کنند. برای درک بهتر این دو مفهوم، باید تفاوت‌ها، کاربردها و نحوه تعامل آن‌ها را بررسی کنیم.

 

مقایسه ایتراتورها و ایتریبل‌ها

 

تفاوت‌ها بین ایتراتورها و ایتریبل‌ها

 

  1. تعریف:
    • ایتریبل (Iterable): هر شی‌ای که می‌توان روی آن تکرار انجام داد و متد __iter__() را پیاده‌سازی کرده باشد.
    • ایتراتور (Iterator): شی‌ای که متد __iter__() را بازمی‌گرداند و همچنین دارای متد __next__() است که مقدار بعدی را تولید می‌کند.
  2. مثال:
    • لیست، رشته و دیکشنری ایتریبل هستند، اما ایتراتور نیستند. برای تکرار روی این ایتریبل‌ها، باید از تابع iter() استفاده کنید تا ایتراتور مربوطه ایجاد شود.
  3. مصرف‌پذیری:
    • ایتریبل‌ها: می‌توان چندین بار از آن‌ها استفاده کرد، زیرا داده‌ها به‌طور کامل ذخیره شده‌اند.
    • ایتراتورها: تنها یک‌بار قابل استفاده هستند. پس از پیمایش کامل، باید ایتراتور جدیدی ایجاد کنید.
  4. دسترسی به داده‌ها:
    • ایتریبل‌ها می‌توانند با استفاده از اندیس‌دهی به داده‌ها دسترسی مستقیم داشته باشند (مثل لیست‌ها).
    • ایتراتورها داده‌ها را به‌صورت ترتیبی و مرحله‌به‌مرحله تولید می‌کنند و دسترسی تصادفی ندارند.

 

موارد استفاده

  1. ایتریبل‌ها:
    • مناسب برای ذخیره‌سازی داده‌های ثابت و قابل استفاده در چندین حلقه.
    • می‌توانند برای انجام عملیات پیچیده‌تر، مانند فیلتر کردن یا جستجو، استفاده شوند.
  2. ایتراتورها:
    • مناسب برای پردازش داده‌های بزرگ که در حافظه جا نمی‌شوند.
    • ایده‌آل برای تولید داده‌ها به‌صورت پویا، مثل دنباله‌های بی‌نهایت یا داده‌های محاسباتی.

 

مثال‌های کاربردی

ایتریبل:

 

my_list = [1, 2, 3, 4]
for item in my_list:
    print(item)
# خروجی:
# ۱
# ۲
# ۳
# ۴

 

در این مثال، لیست یک ایتریبل است که متد __iter__() را پیاده‌سازی کرده و امکان استفاده در حلقه for را فراهم می‌کند.

 

ایتراتور:

 

my_list = [1, 2, 3, 4]
my_iterator = iter(my_list)

print(next(my_iterator))  # خروجی: ۱
print(next(my_iterator))  # خروجی: ۲

 

در اینجا، تابع iter() یک ایتراتور از لیست ایجاد کرده است که مقادیر را به ترتیب بازمی‌گرداند.

 

کاربرد ایتراتورها برای داده‌های پویا:

 

def infinite_counter(start):
    while True:
        yield start
        start += 1

counter = infinite_counter(1)
for _ in range(5):
    print(next(counter))
# خروجی:
# ۱
# ۲
# ۳
# ۴
# ۵


چگونه ایتراتورهای ناهمگام در پایتون را کدنویسی کنیم؟

در برنامه‌نویسی مدرن، مدیریت عملیات ناهمگام (Asynchronous) یکی از چالش‌ها و نیازهای اساسی است. در پایتون، ایتراتورهای ناهمگام (Asynchronous Iterators) ابزارهایی قدرتمند برای مدیریت تکرار داده‌ها در محیط‌های ناهمگام هستند. این ایتراتورها به شما امکان می‌دهند که روی داده‌هایی که به‌صورت ناهمگام تولید می‌شوند (مانند درخواست‌های شبکه یا خواندن فایل‌های بزرگ) بهینه‌تر عمل کنید.

 

کدنویسی ایتراتورهای ناهمگام

 

پیاده‌سازی تکرار ناهمگام در پایتون

ایتراتورهای ناهمگام مشابه ایتراتورهای معمولی عمل می‌کنند، اما به جای استفاده از متد __next__(), متد __anext__() را پیاده‌سازی می‌کنند. این متد باید یک شیء awaitable بازگرداند و در محیط ناهمگام با استفاده از کلمه کلیدی await اجرا شود.

 

۱.تعریف ایتراتور ناهمگام: یک ایتراتور ناهمگام با دو متد زیر تعریف می‌شود:

 

    1. __aiter__(): این متد، شیء ایتراتور را بازمی‌گرداند.
  • __anext__(): این متد، مقدار بعدی را به صورت ناهمگام تولید می‌کند.

 

import asyncio

class AsyncCounter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    async def __aiter__(self):
        return self

    async def __anext__(self):
        if self.current > self.end:
            raise StopAsyncIteration
        await asyncio.sleep(1)  # شبیه‌سازی عملیات ناهمگام
        self.current += 1
        return self.current - 1

async def main():
    async for number in AsyncCounter(1, 5):
        print(number)

asyncio.run(main())
# خروجی:
# ۱
# ۲
# ۳
# ۴
# ۵

 

در این مثال، یک شمارنده ناهمگام تعریف شده است که هر مقدار را پس از یک ثانیه تأخیر بازمی‌گرداند.

 

 

۲.استفاده از ایتراتورهای ناهمگام با منابع خارجی

 

از ایتراتورهای ناهمگام می‌توان برای پردازش داده‌هایی که به‌صورت بلادرنگ از منابع خارجی مانند APIها یا فایل‌های بزرگ دریافت می‌شوند، استفاده کرد.

 

import aiohttp
import asyncio

class AsyncAPIClient:
    def __init__(self, urls):
        self.urls = iter(urls)

    async def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            url = next(self.urls)
        except StopIteration:
            raise StopAsyncIteration
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                return await response.text()

async def main():
    urls = ["//example.com", "//httpbin.org/get"]
    async for data in AsyncAPIClient(urls):
        print(data)

asyncio.run(main())

 

در این مثال، از aiohttp برای ارسال درخواست‌های ناهمگام به URLها و دریافت پاسخ استفاده شده است.

 

 

مزایای ایتراتورهای ناهمگام

 

  1. بهینه‌سازی عملکرد: امکان انجام عملیات همزمان بدون مسدود کردن جریان اصلی برنامه.
  2. مدیریت داده‌های لحظه‌ای: مناسب برای کار با منابعی که به‌صورت تدریجی داده‌ها را تولید می‌کنند.
  3. کاهش مصرف حافظه: داده‌ها به‌صورت مرحله‌به‌مرحله پردازش می‌شوند و نیازی به نگهداری تمام داده‌ها در حافظه نیست.

 

سوالات متداول

سوالات متداول

 

  1. تفاوت اصلی بین ایتراتورها و ایتریبل‌ها چیست؟
    ایتریبل‌ها اشیایی هستند که می‌توان روی آن‌ها تکرار انجام داد، در حالی که ایتراتورها اشیایی هستند که متد __next__() را پیاده‌سازی کرده و عناصر ایتریبل را یکی‌یکی بازمی‌گردانند.

  2. چه زمانی باید از ایتراتورها استفاده کنم؟
    ایتراتورها زمانی مناسب هستند که بخواهید داده‌ها را به صورت مرحله‌به‌مرحله پردازش کنید یا با مجموعه داده‌های بزرگ کار کنید که نمی‌توان آن‌ها را به‌طور کامل در حافظه بارگذاری کرد.

  3. آیا می‌توان یک ایتراتور را چندین بار استفاده کرد؟
    خیر، ایتراتورها یک‌بار مصرف هستند. پس از پردازش تمام عناصر، باید ایتراتور جدیدی ایجاد کنید تا دوباره به داده‌ها دسترسی داشته باشید.

  4. جنریتورها چه تفاوتی با ایتراتورها دارند؟
    جنریتورها نوع خاصی از ایتراتورها هستند که با استفاده از کلمه کلیدی yield تعریف می‌شوند. آن‌ها کدنویسی را ساده‌تر و مصرف حافظه را بهینه‌تر می‌کنند.

  5. ایتراتورهای ناهمگام چه کاربردهایی دارند؟
    ایتراتورهای ناهمگام برای مدیریت عملیات ناهمگام مانند خواندن داده‌های لحظه‌ای از یک API، پردازش فایل‌های بزرگ یا کار با منابعی که به‌صورت غیرهمزمان داده تولید می‌کنند، استفاده می‌شوند.

 

جمع‌بندی

در این مقاله، به یکی از مفاهیم بنیادین و قدرتمند زبان پایتون، یعنی ایتراتورها و ایتریبل‌ها (Iterators and Iterables in Python) پرداختیم. با یادگیری این مفاهیم، شما می‌توانید فرآیند تکرار روی داده‌ها را بهینه‌تر و دقیق‌تر مدیریت کنید و از مزایای آن برای کار با مجموعه‌های داده کوچک و بزرگ بهره ببرید.

در ابتدا، تفاوت بین ایتریبل‌ها و ایتراتورها را توضیح دادیم و نشان دادیم که چگونه این دو مفهوم در کنار یکدیگر عمل می‌کنند. سپس روش‌های مختلف برای ایجاد ایتراتورهای سفارشی، استفاده از جنریتورها و حتی ایتراتورهای ناهمگام را بررسی کردیم. همچنین، محدودیت‌ها و چالش‌های ایتراتورها را شناسایی کردیم و تکنیک‌هایی برای مدیریت بهتر آن‌ها ارائه دادیم.

ایتراتورها و ایتریبل‌ها ابزارهای مهمی برای بهینه‌سازی حافظه، مدیریت داده‌های بزرگ و نوشتن کدهای تمیزتر هستند. با استفاده از این مفاهیم، می‌توانید از قابلیت‌های پیشرفته پایتون به بهترین شکل ممکن بهره‌مند شوید.

اگر این مقاله شما را به یادگیری عمیق‌تر علاقه‌مند کرده است، پیشنهاد می‌کنیم به آموزش زبان پایتون مراجعه کنید. در این صفحه می‌توانید منابع آموزشی بیشتری پیدا کنید و مهارت‌های خود را در پایتون به سطح بالاتری ارتقا دهید. 😊🚀

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

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