[المستوى: متوسط] المقال ده موجّه للمستوى المتوسط. لو لسه ما اشتغلتش بـ Python في مشروع بيقرأ ملفات أكبر من حجم الـ RAM، خد جولة سريعة على for loops و list comprehensions الأول، ثم ارجع.
لو سكربت Python بتاعك بيقع OOM وانت بتقرأ CSV بـ 10 مليون صف أو ملف logs بـ 8GB، المشكلة مش في حجم البيانات. المشكلة إنك بتحمّلها كلها في الذاكرة دفعة واحدة وانت محتاج صف واحد كل مرة. yield بيحل المشكلة دي بتعديل سطرين، وبيوفر 95% من الذاكرة في الحالات النمطية.
Generators في Python: قراءة Stream بدل تحميل List
المشكلة باختصار
تخيّل سيناريو واقعي: عندك ملف logs بحجم 8GB من سيرفر إنتاج، وعايز تعدّ كم سطر فيه كلمة ERROR. الكود البديهي اللي معظم الناس بتكتبه:
def count_errors(path):
lines = open(path).readlines() # 8GB في الذاكرة
return sum(1 for line in lines if "ERROR" in line)
على لابتوب بـ 16GB RAM ده ممكن يقع OOM. السبب بسيط: readlines() بيرجع list فيها كل أسطر الملف مرة واحدة. أنت بتدفع تكلفة 8GB ذاكرة + overhead للـ list علشان في الآخر تشتغل على سطر واحد فقط في كل لحظة. ركز في النقطة دي، لأنها أساس المشكلة كلها.
المفهوم بمثال للمبتدئ
تخيّل عندك بائع كنافة مشهور. عميل طلب 1000 قطعة لحفلة. عند البائع طريقتين يخدمه بيهم:
- طريقة الـ List: يقعد يحضّر الـ 1000 قطعة كلها، يحطهم في صنية ضخمة، يستنى لحد ما يخلصهم، وبعدين يديك الصنية كلها مرة واحدة. لو الصنية مش كبيرة كفاية، الكنافة بتقع.
- طريقة الـ Generator: يحضّر قطعة واحدة، يديهالك في يدك، تاكلها، يحضّر التانية، يديهالك، وهكذا. صنية صغيرة واحدة كفاية مهما كان عدد القطع.
الفرق مش في عدد القطع اللي هتاكلها في النهاية، الفرق في "إمتى" تتحضّر. الـ List بيحضّر الكل مقدمًا قبل ما تبدأ. الـ Generator بيحضّر القطعة وقت ما تطلبها بالظبط، ولا قبل كده ولا بعد كده.
التعريف العلمي بدقة
الـ Generator في Python هو نوع خاص من الـ iterator بيتم إنشاؤه من دالة فيها كلمة yield. كل مرة التنفيذ يوصل لـ yield، الدالة بتعمل pause وتحفظ حالتها كاملة (المتغيرات المحلية + موضع التنفيذ + الـ stack). لما تطلب القيمة التالية بـ next() أو من خلال for loop، التنفيذ بيكمل من نفس النقطة بنفس الحالة.
الآلية دي اسمها lazy evaluation: القيم بتتحسب على الطلب فقط، مش مقدمًا. النتيجة العملية: استهلاك ذاكرة ثابت بغض النظر عن حجم الـ stream، بدل في حالة الـ list. الافتراض هنا إن العنصر الواحد يقدر يدخل الذاكرة، وده شبه دايمًا صحيح في معظم البيانات الجدولية.