🔒

سجّل للوصول إلى الدروس

للاستمرار في تعلم بايثون، يرجى إنشاء حساب مجاني أو تسجيل الدخول إلى حسابك الحالي.

وصول مجاني لجميع دروس بايثون
حفظ تقدمك تلقائياً
بيئة تجربة بايثون التفاعلية
الدرس 16

المولدات والمُكررات

تعلم كيفية معالجة البيانات الضخمة بكفاءة عالية في الذاكرة باستخدام المولدات

📚 الوحدة 4: الدوال ⏱️ 18 دقيقة قراءة

ما هي المولدات؟

المولدات هي دوال تُنتج سلسلة من القيم عبر الزمن، بدلاً من حساب وإرجاع جميع القيم دفعة واحدة. إنها موفرة للذاكرة عند التعامل مع مجموعات بيانات كبيرة.

Python الفرق بين الدالة العادية والمولد
# الدالة العادية - تُرجع الكل دفعة واحدة
def الحصول_على_قائمة_أرقام(ن):
    الناتج = []
    for i in range(ن):
        الناتج.append(i * 2)
    return الناتج

# دالة المولد - تُنتج واحداً تلو الآخر
def الحصول_على_مولد_أرقام(ن):
    for i in range(ن):
        yield i * 2  # yield بدلاً من return

# الاستخدام يبدو متشابهاً
ناتج_القائمة = الحصول_على_قائمة_أرقام(5)
ناتج_المولد = الحصول_على_مولد_أرقام(5)

print(ناتج_القائمة)                # [0, 2, 4, 6, 8]
print(ناتج_المولد)                 # 
print(list(ناتج_المولد))           # [0, 2, 4, 6, 8]

# لكن المولدات كسولة - القيم تُحسب عند الطلب
مولد = الحصول_على_مولد_أرقام(5)
print(next(مولد))  # 0
print(next(مولد))  # 2
print(next(مولد))  # 4
# كل قيمة تُحسب فقط عند الطلب!

الكلمة المفتاحية yield

Python كيف تعمل yield
# yield توقف الدالة، تحفظ حالتها، وتُرجع قيمة
def العد_التنازلي(ن):
    print("بدء العد التنازلي!")
    while ن > 0:
        yield ن
        ن -= 1
    print("انطلاق!")

# إنشاء المولد
العداد = العد_التنازلي(5)

# كل next() يستأنف من حيث توقف
print(next(العداد))  # بدء العد التنازلي! \n 5
print(next(العداد))  # 4
print(next(العداد))  # 3
print(next(العداد))  # 2
print(next(العداد))  # 1
# print(next(العداد))  # انطلاق! \n StopIteration

# الأكثر شيوعاً: استخدام في حلقة for
for رقم in العد_التنازلي(3):
    print(رقم)
# بدء العد التنازلي!
# 3
# 2
# 1
# انطلاق!

كفاءة الذاكرة

Python مقارنة استهلاك الذاكرة
import sys

# القائمة تخزن كل شيء في الذاكرة
def مربعات_قائمة(ن):
    return [س**2 for س in range(ن)]

# المولد يحسب عند الطلب
def مربعات_مولد(ن):
    for س in range(ن):
        yield س**2

# مقارنة استهلاك الذاكرة
ن = 1000000

قائمة_مربعات = مربعات_قائمة(ن)
مولد_مربعات = مربعات_مولد(ن)

print(f"حجم القائمة: {sys.getsizeof(قائمة_مربعات):,} بايت")
# حجم القائمة: 8,448,728 بايت

print(f"حجم المولد: {sys.getsizeof(مولد_مربعات):,} بايت")
# حجم المولد: 112 بايت (ثابت!)

# المولد لا يزال يُنتج نفس القيم
# فقط لا يخزنها جميعاً في الذاكرة
🎯 متى تستخدم المولدات
  • مجموعات بيانات كبيرة: معالجة ملايين السجلات
  • البيانات المتدفقة: قراءة ملفات كبيرة سطراً بسطر
  • التسلسلات اللانهائية: تدفقات بيانات لا تنتهي
  • معالجة خط الأنابيب: سلسلة من تحويلات البيانات

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

مثل قوائم الاستيعاب، لكن تُنشئ مولدات:

Python تعبيرات المولدات
# قائمة استيعاب (تستخدم [])
قائمة_مربعات = [س**2 for س in range(10)]

# تعبير مولد (يستخدم ())
مولد_مربعات = (س**2 for س in range(10))

print(type(قائمة_مربعات))  # 
print(type(مولد_مربعات))   # 

# استخدام تعبير المولد مباشرة في الدوال
المجموع = sum(س**2 for س in range(10))  # لا حاجة لأقواس إضافية
print(f"مجموع المربعات: {المجموع}")  # 285

# مع شرط
مربعات_زوجية = (س**2 for س in range(10) if س % 2 == 0)
print(list(مربعات_زوجية))  # [0, 4, 16, 36, 64]

# إيجاد أول تطابق (كفاءة مع المولد)
الأرقام = (س for س in range(1000000))
أول_فوق_100 = next(س for س in الأرقام if س > 100)
print(أول_فوق_100)  # 101 (توقف مبكراً!)

yield المتعددة و yield from

Python yield المتعددة والتفويض
# yield متعددة في فروع مختلفة
def إنتاج_متعدد():
    yield "الأول"
    yield "الثاني"
    for i in range(3):
        yield f"حلقة {i}"
    yield "الأخير"

for قيمة in إنتاج_متعدد():
    print(قيمة)
# الأول، الثاني، حلقة 0، حلقة 1، حلقة 2، الأخير

# yield from - التفويض لمولد آخر
def أرقام():
    yield 1
    yield 2
    yield 3

def حروف():
    yield 'أ'
    yield 'ب'
    yield 'ج'

def مدمج():
    yield from أرقام()
    yield from حروف()
    yield "النهاية"

print(list(مدمج()))
# [1, 2, 3, 'أ', 'ب', 'ج', 'النهاية']

# تسطيح القوائم المتداخلة مع yield from
def تسطيح(متداخلة):
    for عنصر in متداخلة:
        if isinstance(عنصر, list):
            yield from تسطيح(عنصر)  # مولد تكراري
        else:
            yield عنصر

متداخلة = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
print(list(تسطيح(متداخلة)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

فهم المُكررات

المولدات هي نوع من المُكررات. دعنا نفهم بروتوكول المُكرر:

Python بروتوكول المُكرر
# بروتوكول المُكرر: __iter__ و __next__

# القوائم قابلة للتكرار، لكنها ليست مُكررات
الأرقام = [1, 2, 3]
print(hasattr(الأرقام, '__iter__'))  # True (قابل للتكرار)
# print(next(الأرقام))  # TypeError - القائمة ليست مُكرر

# الحصول على مُكرر من القائمة
المُكرر = iter(الأرقام)
print(next(المُكرر))  # 1
print(next(المُكرر))  # 2
print(next(المُكرر))  # 3
# print(next(المُكرر))  # StopIteration

# ما تفعله حلقة for داخلياً:
العناصر = [1, 2, 3]
# for عنصر in العناصر:
#     print(عنصر)

# مكافئ لـ:
المُكرر = iter(العناصر)
while True:
    try:
        عنصر = next(المُكرر)
        print(عنصر)
    except StopIteration:
        break

# إنشاء فئة مُكرر مخصصة
class عداد:
    def __init__(self, البداية, النهاية):
        self.الحالي = البداية
        self.النهاية = النهاية

    def __iter__(self):
        return self

    def __next__(self):
        if self.الحالي >= self.النهاية:
            raise StopIteration
        القيمة = self.الحالي
        self.الحالي += 1
        return القيمة

for رقم in عداد(1, 5):
    print(رقم)  # 1, 2, 3, 4

أمثلة عملية على المولدات

قراءة الملفات الكبيرة

Python قراءة ملف سطراً بسطر
def قراءة_ملف_كبير(مسار_الملف):
    """قراءة الملف سطراً بسطر (كفاءة في الذاكرة)."""
    with open(مسار_الملف, 'r', encoding='utf-8') as ملف:
        for سطر in ملف:
            yield سطر.strip()

# الاستخدام (الملف يمكن أن يكون جيجابايت)
# for سطر in قراءة_ملف_كبير('ملف_ضخم.txt'):
#     معالجة(سطر)  # سطر واحد في كل مرة

# تصفية وتحويل أثناء القراءة
def تحليل_ملف_سجلات(مسار_الملف):
    """تحليل ملف السجلات، إنتاج الأخطاء فقط."""
    for سطر in قراءة_ملف_كبير(مسار_الملف):
        if 'خطأ' in سطر or 'ERROR' in سطر:
            أجزاء = سطر.split(' - ')
            yield {
                'الوقت': أجزاء[0] if أجزاء else '',
                'المستوى': 'خطأ',
                'الرسالة': أجزاء[-1] if أجزاء else سطر
            }

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

Python مولدات لانهائية
# عداد لانهائي
def عداد_لانهائي(البداية=0):
    ن = البداية
    while True:
        yield ن
        ن += 1

# الحصول على أول 5 قيم
العداد = عداد_لانهائي()
for _ in range(5):
    print(next(العداد))  # 0, 1, 2, 3, 4

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

# الحصول على أول 10 أرقام فيبوناتشي
فيب = فيبوناتشي()
أول_10 = [next(فيب) for _ in range(10)]
print(أول_10)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# استخدام itertools للتسلسلات اللانهائية
from itertools import count, cycle, repeat

# count: 0, 1, 2, 3, ... (لانهائي)
for i, رقم in enumerate(count(10)):
    if i >= 5:
        break
    print(رقم)  # 10, 11, 12, 13, 14

# cycle: يكرر التسلسل للأبد
الألوان = cycle(['أحمر', 'أخضر', 'أزرق'])
print([next(الألوان) for _ in range(7)])
# ['أحمر', 'أخضر', 'أزرق', 'أحمر', 'أخضر', 'أزرق', 'أحمر']

خط أنابيب البيانات

Python سلسلة معالجة البيانات
# سلسلة مولدات لمعالجة البيانات
def قراءة_البيانات():
    """محاكاة قراءة البيانات."""
    البيانات = [
        "  أحمد, 25, القاهرة  ",
        "  سارة, 30, الرياض  ",
        "  محمد, 35, دبي  ",
        "سطر غير صالح",
        "  ليلى, 28, الدار البيضاء  "
    ]
    for سطر in البيانات:
        yield سطر

def تنظيف_البيانات(الأسطر):
    """إزالة المسافات الزائدة."""
    for سطر in الأسطر:
        yield سطر.strip()

def تحليل_البيانات(الأسطر):
    """تحويل إلى قواميس."""
    for سطر in الأسطر:
        أجزاء = سطر.split(', ')
        if len(أجزاء) == 3:
            yield {
                'الاسم': أجزاء[0],
                'العمر': int(أجزاء[1]),
                'المدينة': أجزاء[2]
            }

def تصفية_العمر(السجلات, الحد_الأدنى):
    """تصفية حسب الحد الأدنى للعمر."""
    for سجل in السجلات:
        if سجل['العمر'] >= الحد_الأدنى:
            yield سجل

# بناء خط الأنابيب
خط_الأنابيب = تصفية_العمر(
    تحليل_البيانات(
        تنظيف_البيانات(
            قراءة_البيانات()
        )
    ),
    الحد_الأدنى=30
)

# معالجة النتائج
for شخص in خط_الأنابيب:
    print(شخص)
# {'الاسم': 'سارة', 'العمر': 30, 'المدينة': 'الرياض'}
# {'الاسم': 'محمد', 'العمر': 35, 'المدينة': 'دبي'}

دوال itertools المفيدة

Python أدوات التكرار القوية
from itertools import (
    chain, islice, takewhile, dropwhile,
    groupby, combinations, permutations
)

# chain - دمج تكرارات متعددة
قائمة1 = [1, 2, 3]
قائمة2 = [4, 5, 6]
مدمج = chain(قائمة1, قائمة2)
print(list(مدمج))  # [1, 2, 3, 4, 5, 6]

# islice - تقطيع مُكرر
الأرقام = iter(range(100))
أول_خمسة = islice(الأرقام, 5)
print(list(أول_خمسة))  # [0, 1, 2, 3, 4]

# takewhile - أخذ طالما الشرط صحيح
أرقام = [1, 3, 5, 7, 2, 4, 6]
الناتج = takewhile(lambda س: س < 6, أرقام)
print(list(الناتج))  # [1, 3, 5]

# dropwhile - تخطي طالما الشرط صحيح
الناتج = dropwhile(lambda س: س < 6, أرقام)
print(list(الناتج))  # [7, 2, 4, 6]

# groupby - تجميع العناصر المتتالية
البيانات = [('أ', 1), ('أ', 2), ('ب', 3), ('ب', 4), ('أ', 5)]
for مفتاح, مجموعة in groupby(البيانات, key=lambda س: س[0]):
    print(f"{مفتاح}: {list(مجموعة)}")
# أ: [('أ', 1), ('أ', 2)]
# ب: [('ب', 3), ('ب', 4)]
# أ: [('أ', 5)]

# combinations و permutations
العناصر = ['أ', 'ب', 'ج']
print(list(combinations(العناصر, 2)))
# [('أ', 'ب'), ('أ', 'ج'), ('ب', 'ج')]

print(list(permutations(العناصر, 2)))
# [('أ', 'ب'), ('أ', 'ج'), ('ب', 'أ'), ('ب', 'ج'), ('ج', 'أ'), ('ج', 'ب')]

🎯 تحدي: مولد الأعداد الأولية

أنشئ مولداً يُنتج الأعداد الأولية بلا نهاية:

  • ابدأ من 2
  • أنتج كل عدد أولي
  • استخدمه للحصول على أول 20 عدداً أولياً
💡 عرض الحل
def هل_أولي(ن):
    """التحقق إذا كان ن أولياً."""
    if ن < 2:
        return False
    if ن == 2:
        return True
    if ن % 2 == 0:
        return False
    for i in range(3, int(ن ** 0.5) + 1, 2):
        if ن % i == 0:
            return False
    return True

def مولد_الأعداد_الأولية():
    """إنتاج الأعداد الأولية بلا نهاية."""
    yield 2
    رقم = 3
    while True:
        if هل_أولي(رقم):
            yield رقم
        رقم += 2  # تخطي الأعداد الزوجية

# الحصول على أول 20 عدداً أولياً
الأعداد_الأولية = مولد_الأعداد_الأولية()
أول_20 = [next(الأعداد_الأولية) for _ in range(20)]
print(أول_20)
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

# طريقة بديلة: نهج الغربال للكفاءة
def غربال_الأعداد_الأولية():
    """إنتاج الأعداد الأولية باستخدام غربال إراتوستينس."""
    المركبات = {}
    رقم = 2
    while True:
        if رقم not in المركبات:
            yield رقم
            المركبات[رقم * رقم] = [رقم]
        else:
            for أولي in المركبات[رقم]:
                المركبات.setdefault(أولي + رقم, []).append(أولي)
            del المركبات[رقم]
        رقم += 1

🎯 تحدي: مُكرر الدفعات

أنشئ مولداً يُنتج العناصر في دفعات:

  • يأخذ أي تكرار وحجم الدفعة
  • يُنتج قوائم بحجم الدفعة
  • الدفعة الأخيرة قد تكون أصغر
💡 عرض الحل
def دفعات(التكرار, الحجم):
    """إنتاج دفعات متتالية بالحجم المحدد."""
    المُكرر = iter(التكرار)
    while True:
        عناصر_الدفعة = []
        try:
            for _ in range(الحجم):
                عناصر_الدفعة.append(next(المُكرر))
        except StopIteration:
            if عناصر_الدفعة:
                yield عناصر_الدفعة
            return
        yield عناصر_الدفعة

# اختبار
العناصر = range(10)
for دفعة in دفعات(العناصر, 3):
    print(دفعة)
# [0, 1, 2]
# [3, 4, 5]
# [6, 7, 8]
# [9]

# طريقة بديلة باستخدام islice
from itertools import islice

def دفعات_v2(التكرار, الحجم):
    المُكرر = iter(التكرار)
    while دفعة := list(islice(المُكرر, الحجم)):
        yield دفعة

# استخدام عملي: إدراج دفعات في قاعدة البيانات
المستخدمون = [f"مستخدم_{i}" for i in range(100)]
for دفعة_مستخدمين in دفعات(المستخدمون, 20):
    print(f"إدراج {len(دفعة_مستخدمين)} مستخدم")
    # قاعدة_البيانات.إدراج_متعدد(دفعة_مستخدمين)

📝 ملخص الدرس

  • المولدات تستخدم yield لإنتاج القيم بشكل كسول
  • موفرة للذاكرة - القيم تُحسب عند الطلب
  • تعبيرات المولدات: (س**2 for س in range(10))
  • استخدم yield from للتفويض لمولدات فرعية
  • المُكررات تُنفذ __iter__ و __next__
  • itertools توفر أدوات مولدات قوية
  • رائعة لمجموعات البيانات الكبيرة، التدفقات، والتسلسلات اللانهائية

في الدرس التالي، سنتعلم عن الوحدات والحزم - تنظيم كود بايثون!