Kodax

Generators — Python المتقدم

المولّدات (Generators)

كتابة مكرّر بصنف (كما في الدرس السابق) تحتاج __iter__ و __next__ وحفظ الحالة يدوياً. المولّد يعطيك نفس النتيجة بدالة عادية فيها yield.

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

for n in countdown(3):
    print(n)   # 3 2 1

كيف يعمل yield؟

- استدعاء دالة المولّد لا ينفذ جسمها؛ يرجع كائن مولّد (وهو iterator).

- عند كل next() يُنفَّذ الجسم حتى أول yield، فترجع القيمة ويتجمد المولّد محتفظاً بكل متغيراته.

- الطلب التالي يكمل من حيث توقف.

- عند انتهاء الدالة تُرمى StopIteration تلقائياً.

gen = countdown(2)
print(next(gen))   # 2
print(next(gen))   # 1

return مقابل yield

return تُنهي الدالة وترجع قيمة واحدة. yield ترجع قيمة وتستمر لاحقاً، فتنتج سلسلة قيم.

توفير الذاكرة

def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

مهما كان حجم الملف، في الذاكرة سطر واحد فقط في كل لحظة.

خطوط المعالجة (Pipelines)

مولّدات صغيرة تُركّب كسلسلة، وكل منها يمرر القيم للتالي واحدة واحدة:

lines = read_lines("log.txt")
errors = (l for l in lines if "ERROR" in l)
messages = (l.split(":", 1)[1].strip() for l in errors)

لا يُقرأ أي سطر حتى تبدأ بالمرور على messages.

تعابير المولّد

(x * x for x in range(10)) بأقواس عادية: مولّد بسطر واحد، مثالي داخل sum() و any().

yield from

لتمرير كل قيم مولّد آخر:

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

تسلسلات لا نهائية

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

آمنة لأن القيم تُحسب عند الطلب فقط؛ خذ ما تحتاجه بـ itertools.islice.

متى تستخدم مولّداً؟

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

تمرين

اكتب مولّداً chunks(items, size) يقسم قائمة إلى أجزاء بحجم size، ومولّداً primes() لا نهائياً، واطبع أول 10 أعداد أولية.

# Generator بسيط
def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

# استخدام
for num in count_up_to(5):
    print(num)  # 1, 2, 3, 4, 5

# أو
counter = count_up_to(5)
print(next(counter))  # 1
print(next(counter))  # 2

# فيبوناتشي لانهائي
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
for _ in range(10):
    print(next(fib), end=" ")
# 0 1 1 2 3 5 8 13 21 34

# Generator Expression
squares = (x**2 for x in range(1, 6))
print(list(squares))  # [1, 4, 9, 16, 25]

# مقارنة الذاكرة
import sys

list_comp = [x**2 for x in range(10000)]
gen_exp = (x**2 for x in range(10000))

print(f"List: {sys.getsizeof(list_comp)} bytes")  # ~87000
print(f"Generator: {sys.getsizeof(gen_exp)} bytes")  # ~200

# قراءة ملف كبير
def read_large_file(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

# Pipeline مع generators
def read_numbers(filename):
    for line in read_large_file(filename):
        yield int(line)

def filter_even(numbers):
    for num in numbers:
        if num % 2 == 0:
            yield num

def square(numbers):
    for num in numbers:
        yield num ** 2

# استخدام
# result = square(filter_even(read_numbers("numbers.txt")))
اختبر فهمك