رأينا في جميع ما سبق كيف أن بايثون لغة أمريَّة (Imperative)؛ أي أنها مجموعة متسلسلة من التعليمات البرمجية التي يتبعها المفسِّر حَسَبَ تدفق التحكم.
وفي هذا الفصل نعرف أن بايثون لغة إجرائية (Procedural)؛ وهذا يعني تركيب البرنامج من إجراءات يستدعي كل واحد منها مجموعة أخرى من الإجراءات. فقد يستدعي الإجراء الأول إجرائين، وقد يستدعي كل منهما إجرائين كذلك، …إلخ. حتى تعود النتيجة إلى الإجراء الأوَّل الذي يمثِّل مدخل البرنامج: main. ويتشكل لدينا التسلسل الهرمي كما هو موضح (في الأسفل).
أما القطعة الأخيرة: if __name__ == "__main__" فإن المتغير __name__ هو متغير مخصوص في لغة بايثون تعطيه القيمة __main__ إذا تم تشغيل البرنامج عن طريق هذا الملف، بخلاف ما لو تم استيراد هذا الملف. وسيأتي بيان ذلك في الفصل التالي، عندما نشرح الحزم والوحدات.
نصور كومة الاستدعاءات (Call Stack) بمرور الوقت من اليسار إلى اليمين على النحو التالي:
كومة استدعاءات البرنامج السابق
ملاحظة
توجد مكتبة خارجية لعرض كومة الاستدعاءات حقيقة وهي: SnakeViz وهي تتطلب معرفة بـ cprofile. ويُعرف ذلك بالرسم اللهبي (Flamegraph).
فأي إجراء يتم تعريفه؛ كالمتغير الذي يتم تعريفه: هو نص برمجي محفوظ ينتظر الاستدعاء حتى يحضر في ذاكرة البرنامج في ظرف تنفيذي ويتم تشغيله بعوامل معيَّنة. ثم يعود إلى الإجراء الذي استدعاه، وهكذا دواليك. لذا فإننا إن لم نشتغل الإجراء الأوَّل main فإن البرنامج وإن كان يحفظ هذه الإجراءات إلا أنها تحتاج إلى الاستدعاء لتعمل.
الاستيراد والتصدير
عندما يكبر النص البرمجي ويتعقد، نلجأ لتقسيمه في ملفات منفصلة تؤدي كل منها وظيفة محددة. وذلك حتى يسهل فهم البرنامج، ويسهل التعديل عليه والإضافة.
أو قد نرى أن جزءًا من النص البرمجي يستحق الاستعمال في مشروع آخر، فنريد أن نلفَّه في حزمة، ونصدِّره ليكون قابلاً للتثبيت في مشاريع أخرى.
هاتان الحالتان تستوجبان أن نتعرف على مصطلحين في بايثون: الوحدة والحزمة.
الوحدة (Module) هي النص البرمجي في الملف الواحد (مثل: script.py)
الحزمة (Package) هي مجموعة مكوَّنة من وحدة أو أكثر؛ وتُعرَّف: بمجلَّد يكون فيه ملف خاص باسم __init.py__ ويوضع فيه النص البرمجي المنشئ الذي يعمل عند فعل استيراد الحزمة بالكلمة import (في المرة الأولى فقط).
فيما يلي مثال يوضِّح ترتيب حزمة ما (my_package) مكوَّنة من حزم مضمَّنة، وفي المستوى الأخير نرى الوحدات، على النحو التالي:
الملف main.py فيه الإجراء الأوَّل الذي يمثِّل مدخل البرنامج. ومنه سنستعمل عبارات الاستيراد (import).
نقول إن المجلدين pkg1 وpkg2 هما: حزم فرعية من حزمة my_package، ويمكن استيرادهما بشكل مستقل.
الملفات module_a.py وmodule_b.py وmodule_c.py هي وحدات داخل الحزم الفرعية.
يستخدم الملف __init__.py على أنه علامةً للمجلَّد الذي هو فيه أنه حزمة؛ ولا يلزم أن يتضمن أي أوامر برمجية، ولكنه غالبًا ما يحتوي على تعليمات تهيئة للحزمة. ولذلك سميت init من الإنجليزية Initialization بمعنى التهيئة.
دعنا نلقي نظرة على عمليات الاستيراد واحدة تلو الأخرى، انظر (main.py):
أولاً، لاحظ أن فعل الاستيراد import my_package يحدد حزمة لا وِحدة. وبالتالي، سينفذ الملف __init__.py، الذي يحتوي على عبارات تعيين (مثل __version__، وDEFAULT_TIMEOUT، وما إلى ذلك) سترتبط بمعرف my_package، ويمكن الوصول إليها باستخدام النقطة (.).
ثانيًا لاحظ العبارة: from my_package.pkg1 import module_a فهي تحدد وحدة (module_a)، وبالتالي، يتم تنفيذ جميع التعليمات البرمجية في module_a.py والتعيينات تُسنَد للمعرف module_a ويمكن الوصول إليها باستخدام النقطة (.).
ثالثًا استيراد مباشر للإجراء func_a من وحدة module_a في الحزمة الفرعية pkg2.
أخيرًا الصيغة from ... import * تستورد جميع (*) الأسماء من الوحدة النمطية (باستثناء تلك التي تبدأ بـ بالشرطتين السفليتين: __) إلى نطاق التسمية الحالي (في الوحدة التي نحن فيها). بشكل عام، لا ننصح باستعمال هذه الطريقة، حيث قد تؤدي إلى إلغاء أسماء سبق تعريفها لتحل مكانها هذه الأسماء الجديدة، ولكن لا بأس بها لتقليل جهد الكتابةخصوصًا في الجلسات التفاعلية.
تثبيت حزم
حين لا تجد بايثون اسم الحزمة حاضرًا، فإنها تلجأ إلى البحث في الحزم التي تم تنزيلها وتثبيتها. ولأننا نستعمل uv حيث هو مدير حزم يحافظ على خصوصية الحزم المثبتة لكل مشروع بشكل منفصل، فإننا سنجدها داخل .venv/، وتحديدًا داخل المجلد: .venv/lib/python3.12/site-packages (في حال كنا نستعمل الإصدار 3.12 من بايثون).
لتثبيت حزمة من قاعدة بيانات الحزم PyPI نستعمل أداة uv على النحو التالي: