~/icsd.ir — bash
SYSTEM_ONLINE

Generators و Iterators

Generators و Iterators از قدرتمندترین قابلیت‌های پایتون هستند که اجازه می‌دهند داده‌های بزرگ را به شکل lazy (تنبل) پردازش کنیم - بدون بارگذاری همه چیز در حافظه. در این فصل با yield، generator expressions، itertools و الگوهای lazy evaluation آشنا می‌شویم.

Generators و Iterators از قدرتمندترین قابلیت‌های پایتون هستند که اجازه می‌دهند داده‌های بزرگ را به شکل lazy (تنبل) پردازش کنیم – بدون بارگذاری همه چیز در حافظه. در این فصل با yield، generator expressions، itertools و الگوهای lazy evaluation آشنا می‌شویم.

پروتکل Iterator

هر شیء iterable در پایتون دو متد دارد:

  • __iter__() – یک iterator برمی‌گرداند
  • __next__() – مقدار بعدی را برمی‌گرداند یا StopIteration raise می‌کند
class CountDown:
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

# استفاده
for num in CountDown(3):
    print(num)
# 3
# 2
# 1

# دستی
cd = CountDown(2)
print(next(cd))  # 2
print(next(cd))  # 1
print(next(cd))  # StopIteration

Generators – راه ساده‌تر

به‌جای نوشتن کلاس iterator کامل، از yield استفاده کنید:

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

# همان رفتار CountDown اما با کد بسیار کمتر
for num in countdown(3):
    print(num)
# 3, 2, 1

# Generator یک iterator است
gen = countdown(3)
print(next(gen))  # 3
print(next(gen))  # 2
print(next(gen))  # 1
# print(next(gen))  # StopIteration
تفاوت کلیدی: تابع معمولی return دارد و یک‌باره خروجی می‌دهد. تابع generator با yield در همان نقطه pause می‌شود و در فراخوانی بعدی next() از همان جا ادامه می‌دهد.

Lazy Evaluation – چرا مهم است؟

مقایسه: خواندن فایل ۱۰ گیگابایتی!

# روش بد - همه فایل در حافظه
def read_all_lines(filename):
    with open(filename) as f:
        return f.readlines()  # همه لاین‌ها در RAM

# مصرف حافظه: 10GB - ممکن است crash کند
lines = read_all_lines("huge.log")
for line in lines:
    process(line)

# روش خوب - lazy با generator
def read_lines_lazy(filename):
    with open(filename) as f:
        for line in f:
            yield line  # هر لاین به‌صورت تنبل

# مصرف حافظه: چند KB - کار می‌کند
for line in read_lines_lazy("huge.log"):
    process(line)

Generator Expressions

مثل list comprehension اما با () به‌جای []:

# List comprehension - همه چیز در حافظه
squares_list = [x**2 for x in range(1_000_000)]
# مصرف حافظه: ~30MB

# Generator expression - lazy
squares_gen = (x**2 for x in range(1_000_000))
# مصرف حافظه: ~200 bytes

# هر دو می‌توانند تکرار شوند
total = sum(squares_gen)  # 333332833333500000

# اما generator یک‌بار مصرف است!
total2 = sum(squares_gen)  # 0 - تمام شده

کاربرد در توابع

# بدون پرانتز اضافی وقتی تنها آرگومان است
total = sum(x**2 for x in range(100))
print(any(x > 50 for x in numbers))
print(max(item.price for item in products))

yield from

برای واگذاری کار به generator دیگر:

def numbers_1_to_3():
    yield 1
    yield 2
    yield 3

def numbers_4_to_6():
    yield 4
    yield 5
    yield 6

def all_numbers():
    yield from numbers_1_to_3()
    yield from numbers_4_to_6()

print(list(all_numbers()))  # [1, 2, 3, 4, 5, 6]

پیاده‌سازی flatten بازگشتی

def flatten(nested):
    """تبدیل لیست تو در تو به لیست تخت"""
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)  # recursion
        else:
            yield item

data = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
print(list(flatten(data)))  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

ماژول itertools

ابزارهای آماده برای کار با iterator:

count, cycle, repeat – بی‌نهایت

from itertools import count, cycle, repeat

# count - شمارش از یک عدد
for i in count(start=10, step=2):
    if i > 20:
        break
    print(i)
# 10, 12, 14, 16, 18, 20

# cycle - تکرار بی‌نهایت
colors = cycle(["قرمز", "سبز", "آبی"])
for _ in range(7):
    print(next(colors))
# قرمز, سبز, آبی, قرمز, سبز, آبی, قرمز

# repeat - تکرار یک مقدار
for x in repeat("سلام", times=3):
    print(x)
# سلام, سلام, سلام

chain, zip_longest, product

from itertools import chain, zip_longest, product

# chain - چسباندن iterable‌ها
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
print(list(chain(list1, list2, list3)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# zip_longest - مثل zip اما تا انتهای طولانی‌ترین
names = ["علی", "سارا", "محمد"]
ages = [25, 30]
for n, a in zip_longest(names, ages, fillvalue="نامشخص"):
    print(f"{n}: {a}")
# علی: 25
# سارا: 30
# محمد: نامشخص

# product - ضرب دکارتی
sizes = ["S", "M", "L"]
colors = ["قرمز", "آبی"]
for size, color in product(sizes, colors):
    print(f"{size}-{color}")
# S-قرمز, S-آبی, M-قرمز, M-آبی, L-قرمز, L-آبی

combinations, permutations

from itertools import combinations, permutations

# combinations - بدون ترتیب، بدون تکرار
items = ["A", "B", "C", "D"]
print(list(combinations(items, 2)))
# [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]

# permutations - با ترتیب
print(list(permutations(items, 2)))
# [('A','B'), ('A','C'), ('A','D'), ('B','A'), ('B','C'), ...]

groupby, takewhile, dropwhile

from itertools import groupby, takewhile, dropwhile

# groupby - گروه‌بندی متوالی (نیاز به sort قبلی)
data = [
    ("میوه", "سیب"),
    ("میوه", "موز"),
    ("سبزی", "هویج"),
    ("سبزی", "کاهو"),
]
for category, items in groupby(data, key=lambda x: x[0]):
    print(category, [item[1] for item in items])
# میوه ['سیب', 'موز']
# سبزی ['هویج', 'کاهو']

# takewhile - تا وقتی شرط برقرار است
nums = [1, 3, 5, 8, 9, 2]
print(list(takewhile(lambda x: x < 7, nums)))
# [1, 3, 5]

# dropwhile - رد کردن تا وقتی شرط برقرار است
print(list(dropwhile(lambda x: x < 7, nums)))
# [8, 9, 2]

islice – برش از iterator

from itertools import islice, count

# 10 عدد اول از یک generator بی‌نهایت
infinite = count(1)
first_10 = list(islice(infinite, 10))
print(first_10)  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# مثل slicing لیست‌ها: islice(iter, start, stop, step)
data = range(100)
print(list(islice(data, 10, 20, 2)))
# [10, 12, 14, 16, 18]

ارسال داده به Generator

generator می‌تواند دوطرفه عمل کند:

def counter():
    count = 0
    while True:
        increment = yield count   # دریافت مقدار از send()
        if increment is None:
            increment = 1
        count += increment

c = counter()
print(next(c))      # 0 - راه‌اندازی
print(next(c))      # 1
print(c.send(10))   # 11
print(c.send(5))    # 16
c.close()           # پایان

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

۱. Pagination

def paginate(items, page_size):
    """تقسیم آیتم‌ها به صفحات"""
    page = []
    for item in items:
        page.append(item)
        if len(page) == page_size:
            yield page
            page = []
    if page:  # آخرین صفحه ناتمام
        yield page

# 100 محصول، صفحه‌بندی 10‌تایی
products = list(range(1, 101))
for page_num, page in enumerate(paginate(products, 10), 1):
    print(f"صفحه {page_num}: {page}")

۲. خواندن فایل بزرگ خط به خط

def read_large_csv(filename):
    """خواندن CSV به‌صورت lazy"""
    import csv
    with open(filename, encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield row

# پردازش فایل 10GB بدون مشکل حافظه
total = 0
for row in read_large_csv("sales.csv"):
    total += float(row["amount"])
print(f"مجموع: {total}")

۳. Pipeline داده

def get_lines(filename):
    with open(filename, encoding="utf-8") as f:
        for line in f:
            yield line.strip()

def filter_errors(lines):
    for line in lines:
        if "ERROR" in line:
            yield line

def parse_log(lines):
    for line in lines:
        parts = line.split("|")
        if len(parts) >= 3:
            yield {
                "time": parts[0],
                "level": parts[1],
                "message": parts[2]
            }

# pipeline تنبل - هر لاین یک‌بار از کل خط لوله می‌گذرد
errors = parse_log(filter_errors(get_lines("app.log")))
for error in errors:
    print(error["time"], error["message"])

بهترین شیوه‌ها

  • برای داده‌های بزرگ، همیشه از generator استفاده کنید
  • به جای ساخت کلاس iterator، از yield استفاده کنید
  • generator یک‌بار مصرف است – برای استفاده مجدد دوباره بسازید
  • از itertools به‌جای پیاده‌سازی دستی استفاده کنید
  • generator expression وقتی فقط یک‌بار iterate می‌کنید کارآمدتر است

جمع‌بندی

  • Iterator با __iter__ و __next__ پیاده می‌شود
  • Generator با yield راه ساده‌تر ساخت iterator
  • Lazy evaluation حافظه را به‌شدت کاهش می‌دهد
  • yield from برای واگذاری به generator دیگر
  • itertools ابزارهای آماده برای کار با iterator
  • Pipeline‌های داده‌ای با generator بسیار قدرتمند هستند

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.