سطر واحد، فكرة واحدة
أكيد كتبت هذا النمط أكثر من مرة: تبدأ بقائمة فاضية، تعمل حلقة for على شيء ما، تفلتر بشرط معيّن، وتضيف النتيجة بـ append.
يمكن كتابة نفس الفكرة في سطر واحد باستخدام list comprehension:
اقرأها كما هي: قائمة جديدة مكوّنة من n * 2 لكل n موجود في numbers. الصيغة العامة هي [expression for item in iterable].
هذه الفكرة ليست حكرًا على بايثون، وتُسمّى comprehension (أو "الاستيعاب") لأنك تصف ما الذي يدخل إلى القائمة الجديدة بأسلوب تصريحي، بدل أن تكتب خطوات بنائها خطوة بخطوة.
فلترة القوائم باستخدام list comprehension
أضف شرط if بعد جزء الحلقة لتصفية العناصر:
اقرأ الثاني هكذا: «n * n لكل n في numbers، لكن فقط إذا كان n فردياً».
وهذا ما يقابله باستخدام حلقة for عادية:
كلا الأسلوبين مقبول. لكن الـ list comprehension أقصر، وبعد ما تتعوّد على قراءته يصير فهمه أسرع لأن الهدف واضح من السطر الأول.
الجمع بين map و filter في سطر واحد
تقدر تمرّر القيم عبر دالة وتفلترها في نفس الوقت:
الحلقات المتداخلة داخل Comprehension
استعمال حلقتَي for داخل نفس الـ comprehension يعطيك ناتجًا شبيهًا بالضرب الديكارتي (CARTESIAN PRODUCT) بين المجموعتين:
الترتيب هنا مثل ترتيب الحلقات المتداخلة تمامًا: for الأولى هي الحلقة الخارجية، والثانية هي الداخلية. تقرأها بنفس ترتيب الحلقة المكافئة المكتوبة بمسافات بادئة.
مستويان هما الحد المعقول قبل أن تصبح الحلقة العادية أوضح. إذا وصلت إلى ثلاثة مستويات، ارجع إلى الحلقات التقليدية.
dict comprehension و set comprehension
نفس الفكرة، لكن بأقواس مختلفة:
من النظرة الأولى، قد يبدو set comprehension مطابقًا لـ dict comprehension، لكن الفرق يكمن في وجود key: value مقابل تعبير واحد فقط. أقواس معقوفة مع : تعني قاموسًا، وأقواس معقوفة بدون : تعني مجموعة (set).
generator expression في بايثون
يشبه generator expression إلى حدٍّ بعيد الـ list comprehension، لكنه يستخدم أقواسًا عادية بدلًا من المعقوفة — والأهم أنه لا يُنشئ قائمة فعليًا في الذاكرة:
لاحظ أننا مرّرنا المولّد (generator) مباشرة إلى sum() و any() دون الحاجة إلى أقواس إضافية. تُعدّ تعابير المولّدات (generator expressions) الخيار الأنسب عندما يحتاج المستدعي إلى المرور على العناصر مرة واحدة فقط، فهي أخف على الذاكرة من إنشاء قائمة كاملة حين نتعامل مع مجموعات بيانات ضخمة.
متى تستخدم حلقة for العادية بدلاً من list comprehension؟
تعابير الاختصار مغرية، وتقنياً تستطيع حشر الكثير في سطر واحد، لكن هذا لا يعني أن عليك فعل ذلك.
ارجع إلى حلقة for التقليدية في الحالات التالية:
- عندما تتكوّن العملية من أكثر من خطوة. إن احتجت إلى متغيرات وسيطة، فاكتب الشيفرة بشكل موسّع.
- عندما يوجد تفرّع منطقي معقّد أو معالجة للأخطاء.
- عندما يضطر من يقرأ السطر إلى التوقف أكثر من مرة ليفهم ما يجري.
القاعدة التي أتبعها: إذا استطعت قراءة الـ comprehension بنَفَس واحد وفهم ما يفعله، فأُبقيه كما هو. أما إن تعثّرت في قراءته، فأعيد كتابته على هيئة حلقة عادية.
بعض تعابير الـ comprehension تبدو ذكيّة، لكنها في الواقع تُربك القارئ:
نفس النتيجة. نسخة الحلقة مكوّنة من خمسة أسطر بدل سطر واحد، لكن "الأطول" لا يعني بالضرورة "الأسوأ".
أنماط ستستخدمها كثيرًا
هذه الأنماط الخمسة تغطي جزءاً كبيراً بشكل مفاجئ من العمل اليومي مع البيانات.
ما التالي؟
لقد تعرّفت الآن على أنواع المجموعات الأساسية وعلى الـ comprehension التي تربطها ببعضها. في الفصل القادم: الدوال (functions) — أي كيف تُغلِّف سلوكاً معيناً داخل وحدات لها اسم ويمكن إعادة استخدامها.
الأسئلة الشائعة
ما هو الـ list comprehension في بايثون؟
هو صياغة مختصرة لبناء قائمة جديدة انطلاقًا من أي iterable موجود. مثلًا [x * 2 for x in numbers] ينشئ قائمة جديدة كل عنصر فيها مضروب في 2. وتقدر تضيف فلترة بسهولة: [x for x in numbers if x > 0].
متى يُفضّل تجنّب الـ list comprehension؟
لمّا يضر بوضوح الكود. إذا كان التعبير بالداخل معقد أو فيه تداخل عميق، حلقة for عادية بأسماء متغيرات واضحة تكون أقرأ بكثير. الـ comprehension مناسبة للتحويلات البسيطة — mapping وfiltering فقط. أي شيء أعقد من ذلك، اكتبه في حلقة كاملة.
ما الفرق بين list comprehension و generator expression؟
الـ list comprehension يبني القائمة كاملة في الذاكرة دفعة واحدة، بينما الـ generator expression (نفس الصياغة لكن بأقواس عادية ()) ينتج عنصرًا واحدًا في كل مرة. استخدم الـ generator لمّا تمرّر النتائج لشيء يمر عليها مرة واحدة فقط — مثل sum(...) — حتى ما تبني قائمة كاملة راح ترميها فورًا.