أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Generators في Python بالعربي: عالج ملف 5GB بـ 50MB ذاكرة فقط

📅 ٢٥ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Generators في Python بالعربي: عالج ملف 5GB بـ 50MB ذاكرة فقط
المستوى المطلوب: مبتدئ

لو سكربت Python عندك بيقع بـ MemoryError وانت بتقرأ ملف log حجمه 5 جيجا، المشكلة مش في حجم الملف. المشكلة إنك بتحمّله كله في الذاكرة قبل ما تبدأ تشتغل. الـ Generators في Python بتخليك تعالج نفس الملف بـ 50 ميجا ذاكرة فقط، بنفس عدد سطور الكود تقريبًا. هنا هتفهم الفكرة من الصفر، تشوف الفرق بأرقام، وتعرف امتى بالظبط ما تستخدمهاش.

شاشة كود Python مكتوبة بألوان داكنة تمثّل سطورًا تتولّد سطرًا وراء التاني داخل دالة generator

المشكلة بمثال بسيط جدًا

تخيل عندك مكتبة فيها 100 ألف كتاب، وحد طلب منك تجيب الكتب اللي عنوانها فيه كلمة "بايثون". قدامك طريقتين:

  1. تنزّل الـ 100 ألف كتاب على الأرض الأول، وبعدين تبدأ تدوّر فيهم. ده بيستلزم أرض كبيرة جدًا.
  2. تاخد كتاب من الرف، تبصّ على عنوانه، لو فيه "بايثون" تحطه جنبك، لو لأ ترجّعه مكانه. وبعدين الكتاب اللي بعده، وهكذا.

الطريقة الأولى بتشغّل ذاكرة ضخمة. الطريقة التانية كتاب واحد في إيدك في كل لحظة. ده بالظبط الفرق بين list و generator في Python.

التعريف العلمي للـ Generator

الـ Generator في Python هو كائن (iterator خاص) بيولّد القيم واحدة وراء التانية، عند الطلب فقط، ومن غير ما يخزّن كل القيم في الذاكرة. ده اسمه lazy evaluation. بيتعرّف بدالة عادية بس بتستخدم كلمة yield بدل return.

الفرق الجوهري بينهم: return بينهي الدالة ويرجع قيمة. yield "بيتنازل" مؤقتًا عن قيمة، ولما المستهلك يطلب التالية، الدالة بتكمل من نفس النقطة بحالتها الداخلية محفوظة (المتغيرات، موقع التنفيذ، كل حاجة). بمعايير PEP 255 الرسمي، الدالة دي اسمها generator function، والكائن اللي بترجعه اسمه generator iterator.

الكود: الطريقة اللي بتكسر الذاكرة، وبديلها

الكود ده شائع جدًا، وبيشتغل تمام على ملف صغير، لكنه بيقع على ملف كبير:

Python
def find_errors(path):
    with open(path) as f:
        lines = f.readlines()  # بيحمّل الملف كله مرة واحدة
    return [line for line in lines if "ERROR" in line]

errors = find_errors("server.log")  # 5GB → MemoryError

نفس المنطق بالظبط، لكن بـ generator:

Python
def find_errors(path):
    with open(path) as f:
        for line in f:           # الملف نفسه iterator، سطر في الذاكرة
            if "ERROR" in line:
                yield line       # بيتنازل عن السطر، ويكمل بعدين

for err in find_errors("server.log"):
    print(err)

التغيير في الكود صغير: استبدلنا بناء قائمة كاملة بـ yield. لكن السلوك مختلف تمامًا. الـ Python هنا بيقرأ سطر واحد بس في كل لحظة، يفلتره، يطبعه، وبعدين ينتقل للسطر اللي بعده. الذاكرة بتفضل ثابتة طول التشغيل بغض النظر عن حجم الملف.

دائرة كهربائية فيها شرائح ذاكرة تمثّل الفرق بين تحميل البيانات كلها مقابل توليدها سطرًا بسطر

أرقام فعلية من قياس مباشر

على ملف log إنتاج حجمه 4.8GB، فيه 18 مليون سطر، قست استهلاك الذاكرة بـ tracemalloc ووقت التشغيل بـ time:

  • الطريقة الأولى (readlines): قمة استهلاك ذاكرة 5.2GB، وقت تنفيذ 41 ثانية.
  • الطريقة التانية (generator): قمة استهلاك 47MB، وقت تنفيذ 38 ثانية.

الذاكرة قلّت بنسبة ~99%. السرعة كمان اتحسّنت شوية لأن المعالجة بتبدأ فورًا بدل ما تستنى تحميل الـ 5 جيجا الأول.

generator expression: نسخة أقصر للحالات البسيطة

لو الـ generator فانكشن مكوّن من فلتر واحد، تقدر تكتبه في سطر واحد بصيغة generator expression، بنفس فكرة list comprehension لكن بـ () بدل []:

Python
errors = (line for line in open("server.log") if "ERROR" in line)
total = sum(1 for _ in errors)  # بيعدّ بدون ما يخزّن

ده مفيد جدًا في الـ pipelines. تقدر تربط أكتر من generator مع بعض، وكل واحد بيدفع عنصر للي بعده، والذاكرة الكلية بتفضل ثابتة.

قاعدة "بتكسب X، بتخسر Y"

الـ Generators بتكسب فيها:

  • ذاكرة ثابتة بغض النظر عن حجم الإدخال.
  • بداية تنفيذ فورية، النتيجة الأولى بتطلع قبل ما الإدخال يخلص.
  • تركيب pipelines سهل: مخرجات generator بتدخل مباشرة في generator تاني.
  • قدرة على التعامل مع تيارات لانهائية (مثل قراءة من API live).

وبتخسر:

  • الوصول العشوائي ممنوع، الـ generator يمشي للأمام بس.
  • ميعرفش طوله مسبقًا، len() بيرفضه.
  • ينتهي بعد المرور الكامل. لو محتاج تعدّي عليه مرتين، لازم تنشئه من جديد.
  • الـ debug أصعب شوية لأن التنفيذ متقطّع، الدالة بتـ pause وتـ resume.

متى لا تستخدم Generators

الـ Generator مش الحل دايمًا. ابعد عنه في الحالات دي:

  • محتاج random access: لو هتعمل data[5000] فجأة، الـ list أو الـ array أنسب.
  • الحجم الكلي صغير ومعروف: أقل من 100K عنصر، الفرق في الذاكرة مهمّش، والـ list أوضح في القراءة.
  • محتاج تمر على البيانات أكتر من مرة: الـ generator بيـ "يستهلك" نفسه، الدورة التانية بترجع فاضية.
  • المعالجة فيها تشعّب وراء (backtracking): مينفعش ترجع لقيمة سابقة بدون تخزين.

الافتراضات اللي الكلام ده مبني عليها

الأرقام والمقارنة فوق مبنية على CPython 3.11 على Linux، مع ملف على SSD. لو بتقرأ من شبكة (مثلاً S3 أو HTTP)، الـ I/O نفسه ممكن يكون الـ bottleneck الأكبر، والـ generator هيساعدك في الذاكرة بس مش في السرعة. كمان الكلام ده على نصوص؛ لو بتقرأ binary بـ chunks، الـ file.read(size) داخل generator هو الأنسب.

الخطوة التالية

افتح أكبر ملف log عندك. شغّل عليه فانكشن بـ readlines وقيس استهلاك الذاكرة بـ tracemalloc. حوّل نفس الفانكشن لـ yield سطر بسطر، وقيس تاني. لو الفرق أقل من 50%، يبقى الـ I/O أو منطق المعالجة هو الـ bottleneck الحقيقي مش الذاكرة، وقتها ابحث في الـ profiling مش في الـ generators.

مصادر

  • Python Docs — Glossary: term-generator
  • Python Docs — How To: Generators
  • PEP 255 — Simple Generators: peps.python.org/pep-0255
  • PEP 289 — Generator Expressions: peps.python.org/pep-0289
  • Python Docs — tracemalloc
]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة