FA-TOOLS — Header Component
Generators در پایتون — آموزش کامل با مثال

generators در پایتون — آموزش کامل با مثال

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

اگر دنبال ابزارهای حرفه‌ای پایتون یا اسنیپت‌های آماده برای پروژه‌هاتون هستید، حتماً یه سری به فروشگاه ابزارهای ما در fa-tools.ir بزنید. کلی کد و ابزار کاربردی منتظرتونه!

برای مشاوره یا پشتیبانی فنی، می‌تونید با ما تماس بگیرید: 09202232789

🗺️ نقشه راه: Generators در یک نگاه

Generators در پایتون — آموزش کامل با مثال — تصویر 1

⚡ چیستی Generators

  • ✅ تابع/عبارت که yield داره
  • ✅ “Lazy Evaluation” یا ارزیابی تنبل
  • ✅ تولید داده “در صورت نیاز”

🎯 چرا ازشون استفاده کنیم؟

  • 🚀 صرفه‌جویی در حافظه (برای داده‌های بزرگ)
  • ♾️ مدیریت توالی‌های بی‌نهایت
  • 🧹 کد تمیزتر و قابل خواندن‌تر

💡 مفاهیم کلیدی

  • 🔑 کلمه کلیدی yield
  • ↔️ تفاوت با return
  • 🔄 next() و for loop

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

  • 📤 send(), throw(), close()
  • 🔗 yield from
  • 💡 تولید داده‌های بی‌نهایت

مفهوم Generators: چیستی و چرایی؟

Generators در پایتون — آموزش کامل با مثال — تصویر 2

در پایتون، جنریتورها راهی ساده و کارآمد برای ایجاد Iterators (شیءهای قابل پیمایش) هستند. یک جنریتور در ظاهر شبیه یک تابع معمولیه، اما به جای استفاده از کلمه کلیدی return برای برگرداندن یک مقدار، از کلمه کلیدی yield استفاده می‌کنه.

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

این قابلیت “توقف و از سرگیری” باعث میشه جنریتورها برای تولید دنباله‌هایی از داده‌ها که نیاز به محاسبه دارند، اما نمی‌خواهیم همه‌شون رو یکجا در حافظه نگه داریم، بی‌نظیر باشن. فکر کنید به یک فایل لاگ چندین گیگابایتی؛ آیا منطقیه کلش رو تو حافظه بارگذاری کنیم؟ قطعاً نه!

تفاوت کلیدی: yield در مقابل return

ویژگی Return Yield
نوع تابع تابع معمولی Generator Function
برگرداندن مقدار فقط یکبار، تابع را خاتمه می‌دهد. چندین بار، تابع را به حالت تعلیق در می‌آورد.
حفظ وضعیت وضعیت محلی از دست می‌رود. وضعیت محلی حفظ می‌شود (مثلاً متغیرها).
مصرف حافظه بالا (تمام داده‌ها یکجا) پایین (داده‌ها یکی یکی تولید می‌شوند)
خروجی یک مقدار واحد یک Iterator (که می‌توان روی آن پیمایش کرد)

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

Generators در پایتون — آموزش کامل با مثال — تصویر 3

دلایل زیادی هستن که نشون میدن جنریتورها چقدر مفید و قدرتمندند:

  • صرفه‌جویی در حافظه (Memory Efficiency): مهم‌ترین مزیت. جنریتورها فقط مقداری رو در حافظه نگه می‌دارن که در حال حاضر نیازه. این ویژگی برای کار با داده‌های بزرگ (مثل فایل‌های لاگ، خروجی دیتابیس، یا پردازش تصاویر) حیاتیه. بجای اینکه لیست عظیمی از داده‌ها رو یکجا بسازید، جنریتورها داده‌ها رو “بر حسب نیاز” تولید می‌کنن.
  • ارزیابی تنبل (Lazy Evaluation): داده‌ها فقط زمانی تولید میشن که واقعاً درخواست بشن. این یعنی اگه یه توالی طولانی دارید و فقط به چند مورد اولش نیاز دارید، بقیه محاسبه نمیشن و منابع سیستم ذخیره میشن.
  • سادگی و خوانایی کد: نوشتن Iteratorهای دستی می‌تونه پیچیدگیی زیادی داشته باشه. با yield، این فرآیند خیلی ساده تر میشه.
  • مدیریت توالی‌های بی‌نهایت: جنریتورها می‌تونن توالی‌های بی‌نهایتی رو تولید کنن چون نیازی نیست تمام اعضا رو یکجا در حافظه نگه دارن. این برای شبیه‌سازی‌ها، الگوریتم‌های ریاضی یا تولید داده‌های تست خیلی مفیده.
  • Pipelines: می‌تونید چند جنریتور رو پشت سر هم وصل کنید (مثل یک خط لوله) که هر کدوم روی خروجی قبلی کار کنن. این باعث میشه کدهاتون ماژولارتر و قابل نگهداری باشن.

انواع Generators در پایتون

1. Generator Functions (توابع Generator)

این‌ها توابع عادی هستن که به جای return حداقل یک yield دارن. وقتی این توابع رو صدا می‌زنید، بلافاصله اجرا نمیشن، بلکه یک “شیء جنریتور” برمی‌گردونن. بعد با استفاده از next() روی اون شیء یا با یک حلقه for، شروع به تولید مقادیر می‌کنید.




مثال: یک Generator ساده

def my_first_generator():
    yield "سلام"
    yield "به دنیای"
    yield "Generators"
    yield "خوش آمدید!"

# ساخت یک شیء Generator
gen = my_first_generator()

# استفاده از next() برای دریافت مقادیر
print(next(gen))  # خروجی: سلام
print(next(gen))  # خروجی: به دنیای

# استفاده در حلقه for
print("n--- استفاده در حلقه for ---")
for word in my_first_generator():
    print(word)
    

function copyCode(button) {
const codeBlock = button.previousElementSibling;
const range = document.createRange();
range.selectNode(codeBlock);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand(“copy”);
window.getSelection().removeAllRanges();
button.textContent = “کپی شد!”;
setTimeout(() => {
button.textContent = “کپی”;
}, 2000);
}

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

2. Generator Expressions (عبارات Generator)

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




مثال: Generator Expression

# ساخت یک Generator Expression
gen_exp = (x * x for x in range(5))

print(type(gen_exp)) # خروجی: <class 'generator'>

print(next(gen_exp)) # خروجی: 0
print(next(gen_exp)) # خروجی: 1

# استفاده در حلقه for
print("n--- استفاده در حلقه for ---")
for num in gen_exp:
    print(num)
# خروجی: 4
#         9
#         16
    

همونطور که دیدید، با پرانتز () به جای براکت []، یک Generator Expression می‌سازیم.

کاربردهای عملی و مثال‌های پیشرفته

1. پردازش فایل‌های بزرگ

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




مثال: خواندن فایل‌های حجیم

def read_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip() # برگرداندن هر خط بدون فضای خالی اضافه

# فرض کنید یک فایل بزرگ به نام 'big_log.txt' دارید
# با این روش، هر بار یک خط خونده میشه و در حافظه نگه داشته میشه
# به جای اینکه کل فایل یکجا بارگذاری بشه.

# مثال استفاده (نیاز به وجود فایل):
# for i, line in enumerate(read_large_file('big_log.txt')):
#     if i >= 5: # فقط 5 خط اول
#         break
#     print(f"Line {i}: {line}")

# اگر فایل ندارید، با کد زیر یکی بسازید:
# with open('big_log.txt', 'w') as f:
#     for i in range(100000):
#         f.write(f"This is line number {i}.n")

# حالا می‌تونید جنریتور رو تست کنید:
file_generator = read_large_file('big_log.txt')
print("اولین 3 خط از فایل:")
for _ in range(3):
    try:
        print(next(file_generator))
    except StopIteration:
        print("فایل تمام شد.")
        break
    

2. تولید دنباله‌های بی‌نهایت

یکی از قدرت‌های جنریتورها اینه که می‌تونن مقادیر رو تا ابد تولید کنن. این برای شبیه‌سازی‌ها، ایجاد داده‌های تست یا پیاده‌سازی سری‌های ریاضی مثل فیبوناچی (که به صورت نظری بی‌نهایته) بسیار کاربردیه.




مثال: دنباله فیبوناچی بی‌نهایت

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

fib_gen = fibonacci_generator()

print("اولین 10 عدد فیبوناچی:")
for _ in range(10):
    print(next(fib_gen))
    

3. PipeLine کردن Generators

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




مثال: Pipeline پردازش متن

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

def filter_comments(lines):
    for line in lines:
        if not line.startswith('#') and line: # خطوط خالی و کامنت‌ها رو فیلتر کن
            yield line

def uppercase_lines(lines):
    for line in lines:
        yield line.upper()

# ساخت یک فایل تست
sample_data = """
# این یک کامنت است
خط اول دیتا
خط دوم دیتا
# کامنت دیگر
خط سوم
"""
with open('sample.txt', 'w', encoding='utf-8') as f:
    f.write(sample_data)

# ایجاد Pipeline
# خطوط رو بخون -> کامنت‌ها رو فیلتر کن -> حروف بزرگ کن
processed_lines = uppercase_lines(filter_comments(get_lines('sample.txt')))

print("--- خروجی Pipeline ---")
for line in processed_lines:
    print(line)

# output:
# خط اول دیتا
# خط دوم دیتا
# خط سوم
    

متدهای پیشرفته Generator: ارتباط دو طرفه

جنریتورها فقط برای “تولید” نیستن؛ می‌تونن “دریافت” هم بکنن! این قابلیت با متدهایی مثل send()، throw() و close() معنا پیدا می‌کنه. این قابلیت‌ها جنریتورها رو به Coroutine (روتین‌های مشترک) تبدیل می‌کنن و برای برنامه‌نویسی غیرهمزمان (asynchronous programming) و مدیریت جریان‌های داده پیچیده استفاده میشن.

1. send(value)

متد send() نه تنها جنریتور رو از سر می‌گیره (مثل next())، بلکه یک مقدار رو هم به نقطه yield فعلی ارسال می‌کنه. اون مقدار ارسال شده، نتیجه عبارتی میشه که yield در سمت چپ خودش داره.




مثال: استفاده از send()

def echo_generator():
    received = yield # yield اولی هیچ مقداری برنمی‌گردونه و منتظر دریافت می‌مونه
    while True:
        received = yield received

gen_echo = echo_generator()
next(gen_echo) # برای شروع کردن جنریتور و رسیدن به اولین yield (pre-prime)

print(gen_echo.send("سلام")) # خروجی: سلام
print(gen_echo.send("پایتون")) # خروجی: پایتون
    

2. throw(type, value=None, traceback=None)

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

3. close()

متد close() جنریتور رو خاتمه میده. اگه جنریتور در حال اجرا باشه، یک GeneratorExit استثناء رو raise می‌کنه. این متد برای پاکسازی منابع (مثل بستن فایل‌ها) کاربردیه.

4. yield from (برای Subgenerators)

yield from یک راه حل عالی برای ترکیب جنریتورها با هم (Delegating Generators) هست. این اجازه میده یک جنریتور (Delegating Generator) کنترل رو به یک جنریتور دیگه (Subgenerator) واگذار کنه. به این صورت می‌تونیم کدهای ماژولارتر و خواناتری برای کارهای پیچیده داشته باشیم.




مثال: استفاده از yield from

def sub_generator(x):
    for i in range(x):
        yield i * 2

def main_generator():
    yield "شروع"
    yield from sub_generator(3) # کنترل رو به sub_generator واگذار می‌کنه
    yield "پایان"

for val in main_generator():
    print(val)
# خروجی:
# شروع
# 0
# 2
# 4
# پایان
    

این قابلیت yield from مخصوصاً در سناریوهای پیچیده‌تر که نیاز به ترکیب چند منبع داده یا چندین مرحله پردازش داریم، خیلی به کار میاد. برای دیدن اسنیپت‌های بیشتر در مورد برنامه‌نویسی پایتون و دیگر زبان‌ها، به تمام اسنیپت‌ها سر بزنید.

عیب‌یابی سریع (Troubleshooting)

با اینکه جنریتورها خیلی قدرتمندند، اما گاهی اوقات ممکنه با مشکلاتی روبرو بشیم. در اینجا چند مورد رایج و راه‌حلشون رو بررسی می‌کنیم:

  • مشکل: StopIteration بلافاصله بعد از اولین آیتم.

    توضیح: این اتفاق معمولاً وقتی می‌افته که جنریتور شما در واقع یک آیتم رو برمی‌گردونه و بعدش به پایان می‌رسه.

    راه‌حل: مطمئن شید که حلقه یا شرطی که مقادیر رو yield می‌کنه، به درستی تعریف شده و تا زمانی که واقعاً نیاز به تولید مقادیر دارید، ادامه پیدا می‌کنه. مثلاً while True برای توالی‌های بی‌نهایت یا یک for loop روی یک مجموعه قابل پیمایش.
  • مشکل: جنریتور یکبار مصرفه و بعد از یک بار پیمایش دیگه چیزی تولید نمی‌کنه.

    توضیح: جنریتورها بعد از اینکه تمام مقادیرشون رو تولید کردن، “خالی” میشن و برای استفاده مجدد باید دوباره از تابع جنریتور یک شیء جدید ساخت.

    راه‌حل: اگه نیاز دارید چندین بار روی یک توالی جنریتور پیمایش کنید، هر بار یک شیء جنریتور جدید ایجاد کنید:

    
    def my_gen():
        yield 1
        yield 2
    
    gen1 = my_gen()
    for x in gen1:
        print(x) # 1, 2
    
    gen2 = my_gen() # باید دوباره ساخته بشه
    for x in gen2:
        print(x) # 1, 2
                

  • مشکل: مصرف حافظه همچنان بالاست، با اینکه از جنریتور استفاده می‌کنم.

    توضیح: ممکنه خروجی جنریتور رو دارید در یک لیست ذخیره می‌کنید (مثلاً با list(my_generator())) که هدف استفاده از جنریتور رو نقض می‌کنه.

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

    
    # بد: مصرف حافظه بالا
    # all_data = list(read_large_file('big_log.txt'))
    
    # خوب: مصرف حافظه بهینه
    for line in read_large_file('big_log.txt'):
        # هر خط را پردازش کنید، بدون ذخیره همه در حافظه
        pass
                

  • مشکل: سردرگمی بین yield تنها و value = yield.

    توضیح: وقتی yield X دارید، فقط X رو برمی‌گردونید. اما وقتی Y = yield X دارید، جنریتور X رو برمی‌گردونه و سپس منتظر میمونه تا یک مقدار با send() بهش ارسال بشه، که اون مقدار در Y ذخیره میشه.

    راه‌حل: فقط زمانی از Y = yield X استفاده کنید که واقعاً نیاز به ارسال داده به جنریتور دارید (یعنی دارید از قابلیت Coroutine استفاده می‌کنید).

نتیجه‌گیری

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

پس منتظر چی هستید؟ شروع کنید به استفاده ازشون و تفاوت رو خودتون تجربه کنید! و اگه دنبال اسنیپت‌های مفید و کاربردی برای توسعه‌تون هستید، فراموش نکنید که مخزن اسنیپت‌های fa-tools.ir همیشه در دسترسه.

سوالات متداول (FAQ)

❓ Generator چیست و چه تفاوتی با یک تابع معمولی دارد؟

Generator یک نوع خاص از تابع یا عبارت است که از کلمه کلیدی yield برای برگرداندن مقادیر استفاده می‌کند، برخلاف توابع معمولی که از return استفاده می‌کنند. تفاوت اصلی این است که Generatorها وضعیت خود را پس از هر yield حفظ می‌کنند و می‌توانند بعداً از همان نقطه ادامه پیدا کنند، در حالی که توابع معمولی با return خاتمه می‌یابند.

❓ مهم‌ترین مزیت استفاده از Generators چیست؟

مهم‌ترین مزیت، صرفه‌جویی چشمگیر در مصرف حافظه (Memory Efficiency) است. Generatorها داده‌ها را به صورت “lazy” (تنبل) یا “on-demand” (در صورت نیاز) تولید می‌کنند و نیازی به بارگذاری تمام داده‌ها در حافظه به صورت یکجا ندارند. این برای کار با مجموعه‌های داده بسیار بزرگ یا توالی‌های بی‌نهایت بسیار مفید است.

❓ آیا می‌توان از یک Generator چندین بار استفاده کرد؟

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

❓ Generator Expression چیست؟

Generator Expression یک سینتکس فشرده و تک خطی برای ایجاد Generatorهاست که شبیه به List Comprehension است، اما به جای براکت [] از پرانتز () استفاده می‌کند. هدف آن نیز تولید داده‌ها به صورت تنبل و بهینه از نظر حافظه است.

/* Basic Reset & Font for better readability */
body {
font-family: ‘Vazirmatn’, ‘Arial’, sans-serif; /* Example font, adjust as needed */
direction: rtl; /* For Persian text */
text-align: right;
margin: 0;
padding: 20px;
background-color: #FFFFFF;
color: #34495E;
}

/* General Heading Styles (already inline, but for consistency if needed) */
h1, h2, h3 {
font-family: ‘Vazirmatn’, ‘Arial’, sans-serif;
}

/* Paragraph Styling */
p {
margin-bottom: 1em;
line-height: 1.7;
}

/* List Styling */
ul {
list-style-type: disc;
margin-left: 20px;
padding: 0;
}
ul li {
margin-bottom: 0.5em;
}

/* Code Blocks – for general snippet display */
pre code {
display: block;
padding: 1em;
overflow-x: auto;
border-radius: 5px;
background-color: #2D2D2D; /* Dark background */
color: #F8F8F2; /* Light text */
font-family: ‘Fira Code’, ‘Cascadia Code’, ‘Consolas’, monospace;
font-size: 0.9em;
line-height: 1.5;
}

/* Inline Code Styling */
code {
background-color: #F0F4F7;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: ‘Fira Code’, ‘Cascadia Code’, ‘Consolas’, monospace;
font-size: 0.9em;
color: #C0392B; /* A distinct color for inline code */
}

/* Links */
a {
color: #2980B9;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}

/* Responsive adjustments for overall layout */
@media (max-width: 768px) {
h1 { font-size: 2em !important; }
h2 { font-size: 1.8em !important; }
h3 { font-size: 1.4em !important; }
.info-box, .code-block, table, ul, p {
padding-left: 15px !important;
padding-right: 15px !important;
margin-left: 10px !important;
margin-right: 10px !important;
}
.infographic-item {
flex-basis: 100% !important; /* Stack items on small screens */
}
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr { border: 1px solid #ccc; margin-bottom: 10px; border-radius: 5px; }
td {
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50% !important;
text-align: right;
}
td:before {
position: absolute;
top: 6px;
left: 6px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
content: attr(data-label);
font-weight: bold;
}
td:nth-of-type(1):before { content: “ویژگی”; }
td:nth-of-type(2):before { content: “Return”; }
td:nth-of-type(3):before { content: “Yield”; }
.code-block button {
position: static;
display: block;
width: 100%;
margin-top: 10px;
}
}
@media (max-width: 480px) {
body { padding: 10px; }
h1 { font-size: 1.8em !important; }
h2 { font-size: 1.5em !important; }
h3 { font-size: 1.2em !important; }
}

document.addEventListener(‘DOMContentLoaded’, () => {
// Apply responsive labels to table cells
const table = document.querySelector(‘table’);
if (table) {
const headers = Array.from(table.querySelectorAll(‘thead th’)).map(th => th.textContent);
table.querySelectorAll(‘tbody tr’).forEach(row => {
row.querySelectorAll(‘td’).forEach((td, index) => {
td.setAttribute(‘data-label’, headers[index]);
});
});
}
});

Table of Contents

آخرین نوشته‌ها