🚀 Generators في بايثون: السر وراء الكفاءة والمرونة

مرحباً بك في درس Generators! إذا كنت قد استخدمت الدوال العادية في بايثون، فأنت على وشك اكتشاف نوع خاص من الدوال يتميز بقوة وكفاءة غير عادية. Generators هي واحدة من أكثر الميزات أناقة في بايثون، وستغير طريقة تفكيرك في معالجة البيانات.


🤔 ما هي Generators؟

Generators هي دوال خاصة تستطيع إنتاج سلسلة من القيم واحدة تلو الأخرى، بدلاً من إرجاع قيمة واحدة فقط. الفرق الرئيسي بينها وبين الدوال العادية هو أن المولدات تحتفظ بحالتها بين الاستدعاءات.

تخيل أنك تريد قراءة كتاب ضخم. بدلاً من حمل الكتاب كله مرة واحدة (مثل القوائم)، تقرأه صفحة صفحة. هذا بالضبط ما تفعله Generators!

# الدالة العادية ترجع كل القيم مرة واحدة
def regular_function():
    numbers = [1, 2, 3, 4, 5]
    return numbers

# Generator تنتج القيم واحدة تلو الأخرى
def simple_generator():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5

🔑 الكلمة الأساسية yield

السر وراء Generators هو الكلمة الأساسية yield. عندما تستخدم yield بدلاً من return في دالة، تتحول تلقائياً إلى generator.

كيف تعمل yield:

  • عندما تستدعي الدالة، لا تنفذ الكود فوراً
  • ترجع كائن generator يمكن التكرار عليه
  • عند كل استدعاء لـ next()، تنفذ الدالة حتى تصل إلى yield
  • تتوقف مؤقتاً وتحفظ حالتها
  • تستأنف من حيث توقفت عند الاستدعاء التالي
def countdown(n):
    print("بدء العد التنازلي!")
    while n > 0:
        yield n  # إرجاع القيمة الحالية والتوقف
        n -= 1
    print("انتهى العد!")

# إنشاء generator
counter = countdown(3)

# استخدام next() للحصول على القيم
print(next(counter))  # المخرجات: بدء العد التنازلي! ثم 3
print(next(counter))  # المخرجات: 2
print(next(counter))  # المخرجات: 1
print(next(counter))  # المخرجات: انتهى العد! ثم خطأ StopIteration

🎯 الفرق بين Generators والقوائم العادية

لنفهم لماذا Generators مفيدة، لنقارنها مع القوائم التقليدية:

# الطريقة التقليدية (تستهلك ذاكرة كبيرة)
def get_squares_list(n):
    result = []
    for i in range(n):
        result.append(i * i)
    return result

# استخدام Generator (موفر للذاكرة)
def squares_generator(n):
    for i in range(n):
        yield i * i

# المقارنة في الاستخدام
numbers = 1000000

# القائمة تخزن مليون عنصر في الذاكرة
squares_list = get_squares_list(numbers)

# Generator لا يخزن إلا العنصر الحالي
squares_gen = squares_generator(numbers)

مميزات Generators:

  • توفير الذاكرة: لا تخزن كل القيم مرة واحدة
  • الكفاءة: تنتج القيم عند الحاجة فقط
  • إمكانية التكرار اللانهائي: يمكن إنشاء سلاسل لا نهائية

🔄 استخدام Generators مع الحلقات

أسهل طريقة لاستخدام Generators هي مع الحلقات، حيث تتعامل بايثون تلقائياً مع next() و StopIteration:

def fibonacci_generator(limit):
    a, b = 0, 1
    count = 0
    while count < limit:
        yield a
        a, b = b, a + b
        count += 1

# استخدام generator في حلقة for
print("أول 10 أرقام في متتالية فيبوناتشي:")
for num in fibonacci_generator(10):
    print(num, end=" ")
# المخرجات: 0 1 1 2 3 5 8 13 21 34

✨ Generator Expressions

بايثون توفر صيغة مختصرة لإنشاء Generators تشبه List Comprehensions، ولكن بأقواس دائرية بدلاً من مربعة:

# List Comprehension (يخلق قائمة كاملة)
squares_list = [x*x for x in range(5)]
print(squares_list)  # [0, 1, 4, 9, 16]

# Generator Expression (ينتج قيماً عند الطلب)
squares_gen = (x*x for x in range(5))
print(squares_gen)    # <generator object <genexpr> at 0x...>

# تحويل generator إلى قائمة إذا أردت
print(list(squares_gen))  # [0, 1, 4, 9, 16]

💡 حالات عملية لاستخدام Generators

1. معالجة الملفات الكبيرة:

def read_large_file(filename):
    with open(filename, 'r') as file:
        for line in file:
            yield line.strip()

# قراءة ملف كبير سطراً سطراً دون تحميله كاملاً في الذاكرة
for line in read_large_file('big_data.txt'):
    if 'error' in line:
        print(line)

2. توليد بيانات لا نهائية:

def infinite_counter():
    num = 0
    while True:
        yield num
        num += 1

counter = infinite_counter()
print(next(counter))  # 0
print(next(counter))  # 1
print(next(counter))  # 2
# يمكن الاستمرار إلى ما لا نهاية!

🛑 معالجة استثناء StopIteration

عند انتهاء القيم في Generator، ترفع استثناء StopIteration. الحلقات تتعامل معه تلقائياً، ولكن يمكنك معالجته يدوياً:

def simple_gen():
    yield 1
    yield 2

gen = simple_gen()
try:
    print(next(gen))  # 1
    print(next(gen))  # 2
    print(next(gen))  # StopIteration
except StopIteration:
    print("انتهت القيم في Generator!")

🎓 خلاصة الدرس

تعلمنا اليوم أن Generators هي:

  • دوال تستخدم yield بدلاً من return
  • تنتج قيماً واحدة تلو الأخرى عند الطلب
  • توفر ذاكرة وتحسن الأداء مع البيانات الكبيرة
  • يمكن استخدامها مع الحلقات و Generator Expressions
  • مثالية للملفات الكبيرة والسلاسل اللانهائية