Python Generators للمبتدئ: اقرأ ملف 50GB بـ 8MB رام بدل ما السيرفر يقع
المستوى: مبتدئ
لو سيرفر Python بتاعك بيقع OOM (Out Of Memory) لمّا بتفتح ملف لوج 50GB لتحليل الأخطاء، المشكلة مش في حجم الـ RAM. المشكلة إنك بتحمّل الملف كله دفعة واحدة في الذاكرة بـ file.read() أو file.readlines(). كلمة واحدة اسمها yield بتنزّل استهلاك الذاكرة من 50GB لـ 8MB، على نفس السيرفر، بدون أي تغيير في الهاردوير.
المشكلة باختصار
أي مهندس Python بيشتغل على بيانات حقيقية لازم يوصل لمرحلة بيلاقي فيها نفسه قدّام موقفين:
- ملف CSV فيه 200 مليون صف، حجمه 30GB، محتاج تحسبله إحصائيات.
- API بيرجّع 5 ملايين سجل من قاعدة البيانات، محتاج تحوّلهم لصيغة JSON وترفعهم لـ S3.
الطريقة الشائعة بتفشل في الحالتين، لأنها بتحاول تحمّل كل البيانات في الذاكرة قبل ما تبدأ تشتغل عليها. السيرفر بـ 8GB رام بيقع، والـ container على Kubernetes بياخد OOMKilled، والشغل بيتوقف نص الليل من غير ما حد يلاحظ.
مثال بسيط: الشيف اللي اشترى السوق كله مقابل الشيف اللي بيجيب المكوّن لمّا يحتاجه
تخيّل شيف في مطعم عنده طريقتين علشان يطبخ 1000 طبق خلال السهرة:
- الطريقة الأولى: ينزل السوق ويشتري مكوّنات الـ 1000 طبق دفعة واحدة، ويخزّنهم في المطبخ. المطبخ هيتملي، الخضار اللي مش هيستخدمها قريب هيبوظ، ومفيش مساحة يتحرّك فيها أصلاً.
- الطريقة الثانية: يجيب مكوّنات طبق واحد بس، يطبخه، يقدّمه، وبعدين يجيب مكوّنات الطبق اللي بعده. المطبخ فاضي طول الوقت، وما فيش مكوّن بيبوظ، والشيف عمره ما هيلاقي المطبخ متخن.
الطريقة الثانية هي بالظبط فكرة الـ Generator في Python. الكود بيطلب القيمة دلوقتي بس لمّا يحتاجها، ومش بيخزّن باقي القيم في الذاكرة.
التعريف العلمي الدقيق
الـ Generator في Python هو نوع خاص من الـ iterators بيتعرّف عن طريق دالة فيها كلمة yield بدل return. أوّل ما الكود يوصل لـ yield، الدالة بترجع القيمة وبتحفظ حالتها الكاملة (المتغيّرات، موقع التنفيذ، الـ stack frame) في كائن واحد. أوّل ما حد يطلب القيمة اللي بعدها بـ next() أو بـ for loop، الدالة بتكمّل من نفس النقطة بالظبط، مش من الأول.
المرجع الرسمي: PEP 255 (Simple Generators) اللي اتقبل في Python 2.2 سنة 2001، و PEP 380 اللي ضاف yield from في Python 3.3 لتسهيل تجميع Generators مع بعض.
المثال التنفيذي: قراءة ملف 52GB بـ 8MB ذاكرة
الكود الجاي شغّال على Python 3.12 بدون أي مكتبة خارجية. هنفترض إن عندك ملف access.log بحجم 52GB، وعايز تعدّ كل سطر فيه كلمة ERROR: