أين قمت بتثبيت حزم بايثون؟

أكتب هذا المقال لأنني لاحظت مؤخرًا في مجتمع بايثون وجود العديد من الأسئلة الشائعة:

  • لماذا يؤدي تشغيل الأمر بعد تثبيت pip إلى ظهور خطأ “لم يتم العثور على الملف التنفيذي”؟
  • لماذا يؤدي استيراد وحدة إلى ظهور الخطأ “ModuleNotFound”؟
  • لماذا يمكنني تشغيل الكود في PyCharm، لكنه لا يعمل في موجه الأوامر؟

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

كيف يجد بايثون الحزم

من المحتمل جدًا أن يكون جهاز الكمبيوتر لديك يحتوي على أكثر من تثبيت لـبايثون، بالإضافة إلى العديد من البيئات الافتراضية. وقد يؤدي هذا إلى تجاهل مسار التثبيت عند تثبيت الحزم. دعنا أولاً نتناول مسألة موقع الحزمة. الإجابة على هذا السؤال بسيطة، ولكن قد لا يكون العديد من الأشخاص على دراية بالمبادئ الأساسية.

نفترض أن المسار إلى مُفسِّر بايثون الخاص بك هو $path_prefix/bin/python. عند تشغيل بيئة بايثون التفاعلية أو تشغيل البرنامج النصي باستخدام هذا المُفسِّر، سيتم البحث افتراضيًا في المواقع التالية:

  • $path_prefix/lib (مسار المكتبة القياسي)
  • $path_prefix/lib/pythonX.Y/site-packages (مسار مكتبة الطرف الثالث، حيث يتوافق X.Y مع الإصدار الرئيسي والثانوي من بايثون، مثل 3.7 أو 2.6)
  • دليل العمل الحالي (نتيجة الأمر pwd)

إذا كنت تستخدم بايثون الافتراضي على Linux، فمن المرجح أن يكون $path_prefix هو /usr. إذا قمت بتجميع بايثون بالخيارات الافتراضية بنفسك، فسيكون $path_prefix هو /usr/local. من النقطة الثانية أعلاه، يمكنك أن ترى أن مسارات مكتبات الطرف الثالث تختلف باختلاف إصدارات بايثون. إذا قمت بترقية بايثون من الإصدار 3.6 إلى 3.7، فقد لا تكون مكتبات الطرف الثالث المثبتة للإصدار السابق قابلة للاستخدام. بالطبع، يمكنك نسخ المجلد بالكامل، وفي معظم الحالات، لن يتسبب ذلك في أي مشاكل.

العديد من الدوال المفيدة

  • sys.executable: المسار إلى مُفسِّر بايثون المُستخدم حاليًا.
  • sys.path: قائمة المسارات التي تم البحث فيها عن الحزمة الحالية.
  • sys.prefix: القيمة الحالية لـ $path_prefix.

بالإضافة إلى ذلك، سيؤدي تشغيل python -m site في سطر الأوامر إلى طباعة بعض المعلومات حول بيئة بايثون الحالية، بما في ذلك قائمة مسار البحث.

مثال

>>> import sys
>>> sys.executable
'/home/frostming/.pyenv/versions/3.7.2/bin/python'
>>> sys.path
['', '/home/frostming/.pyenv/versions/3.7.2/lib/python37.zip', '/home/frostming/.pyenv/versions/3.7.2/lib/python3.7', '/home/frostming/.pyenv/versions/3.7.2/lib/python3.7/lib-dynload', '/home/frostming/.local/lib/python3.7/site-packages', '/mnt/d/Workspace/pipenv', '/home/frostming/.pyenv/versions/3.7.2/lib/python3.7/site-packages']
>>> sys.prefix
'/home/frostming/.pyenv/versions/3.7.2'

إضافة مسارات البحث باستخدام متغيرات البيئة

إذا لم يكن المسار إلى الحزمة الخاصة بك مدرجًا في قائمة مسارات البحث المذكورة أعلاه، فيمكنك إضافة المسار إلى متغير بيئة PYTHONPATH. يجب فصل المسارات المتعددة بعلامات نقطتين (فاصلة منقوطة على نظام التشغيل Windows).

ومع ذلك، كن حذرًا من عدم تضمين مسارات لحزم من إصدارات بايثون المختلفة في PYTHONPATH، مثل PYTHONPATH=/home/frostming/.local/lib/python2.7/site-packages. وذلك لأن المسارات في PYTHONPATH لها الأسبقية على مسارات البحث الافتراضية، وقد يؤدي استخدام بايثون 3 إلى مشكلات تتعلق بالتوافق. في الواقع، من الأفضل عدم تضمين أي مسارات تحتوي على site-packages في PYTHONPATH.

بالمناسبة، يتم استخدام PATH للعثور على مسار البحث للبرامج القابلة للتنفيذ. إذا قمت بتشغيل الأمر my_cmd في المحطة الطرفية، فسيقوم النظام بفحص المسارات في PATH واحدًا تلو الآخر للتحقق مما إذا كان my_cmd موجودًا في أي من هذه المسارات. لذا، إذا تلقيت خطأً يقول إنه لا يمكن العثور على برنامج أو لم يتم التعرف على الأمر، فتحقق مما إذا كان المسار قد تمت إضافته إلى PATH.

كيف يقوم بايثون بتثبيت الحزم

حاليًا، الطريقة الأكثر شيوعًا لتثبيت حزم بايثون هي استخدام pip. حتى إذا كنت تستخدم أدوات مثل pipenv أو poetry، فإنها تعتمد في النهاية على pip. التعليمات التالية قابلة للتطبيق عالميًا.

هناك طريقتان لتشغيل pip:

  • pip ...
  • python -m pip ...

الطريقتان الأولى والثانية متشابهتان تمامًا، مع وجود اختلاف يتمثل في أن الطريقة الأولى تستخدم مُفسِّر بايثون المحدد في سطر shebang من نص pip. بشكل عام، إذا كان مسار pip الخاص بك هو $path_prefix/bin/pip، فإن مسار بايثون المقابل هو $path_prefix/bin/python. إذا كنت تستخدم نظام Unix، فيمكنك العثور على مسار مُفسِّر بايثون عن طريق تشغيل cat $(which pip)، وسيحتوي السطر الأول على المسار. تحدد الطريقة الثانية صراحةً موقع بايثون. تنطبق هذه القاعدة على جميع برامج بايثون القابلة للتنفيذ.

لذا، بدون أي إعدادات مخصصة، سيؤدي استخدام pip لتثبيت الحزم إلى تثبيتها تلقائيًا في $path_prefix/lib/pythonX.Y/site-packages (حيث يتم الحصول على $path_prefix من القسم السابق)، وسيتم تثبيت البرامج القابلة للتنفيذ في $path_prefix/bin. إذا كنت تريد تشغيل my_cmd مباشرة من سطر الأوامر، فتذكر إضافته إلى PATH.

الخيارات في pip لتغيير مواقع التثبيت:

  • --prefix PATH: يستبدل $path_prefix بالقيمة المحددة.
  • --root ROOT_PATH: يضيف ROOT_PATH قبل $path_prefix. على سبيل المثال، مع --root /home/frostming، يتغير $path_prefix من /usr إلى /home/frostming/usr.
  • --target TARGET: يحدد موقع التثبيت إلى TARGET بشكل مباشر.

البيئات الافتراضية

يتم إنشاء بيئة افتراضية لعزل التبعيات الخاصة بالمشاريع المختلفة، مما يسمح بتثبيتها في مسارات منفصلة لمنع تعارض التبعيات. بمجرد فهم كيفية تثبيت بايثون للحزم، يصبح فهم المبادئ وراء البيئات الافتراضية (virtualenv، venv module) أمرًا مباشرًا.

في الأساس، سيؤدي تشغيل virtualenv myenv إلى نسخ مُفسِّر بايثون جديد إلى myenv/bin وإنشاء أدلة مثل myenv/lib وmyenv/lib/pythonX.Y/site-packages. عند تنفيذ source myenv/bin/activate، فإنه يضيف myenv/bin إلى مقدمة PATH، مما يضمن إعطاء الأولوية لمُفسِّر بايثون المنسوخ هذا في عمليات البحث. وبالتالي، عند تثبيت الحزم، يصبح $path_prefix هو myenv، مما يحقق العزل في مسارات التثبيت.

تأثير تنفيذ البرنامج النصي على مسارات البحث

كما شرحنا سابقًا، العامل الأساسي الذي يؤثر على ما إذا كان بايثون قادرًا على العثور على حزمة هو sys.path، والأهم من ذلك، مسار sys.executable. بعد كتابة برنامج، تحتاج حتمًا إلى تشغيله. ومع ذلك، قد تؤثر طرق مختلفة لتشغيل البرنامج النصي على sys.path، مما يؤدي إلى سلوكيات مختلفة. دعنا نناقش هذه المشكلة.

بافتراض أن هيكل الحزمة الخاص بك هو كما يلي:

.
├── main.py
└── my_package
    ├── __init__.py
    ├── a.py
    └── b.py

محتوى main.py

import my_package.b

محتوى b.py

import sys
print("I'm b")
print(sys.path)

تنفيذ main.py

$ python main.py
I'm b
['/home/frostming/test_path', ...]  # sommit some common paths which are not related
$ python my_package/b.py
I'm b
['/home/frostming/test_path/my_package', ...]

تُسمى طريقة تشغيل python xxx.py بالتنفيذ المباشر. في هذه الحالة، يتم تعيين قيمة __name__ في الملف إلى __main__. يتم استخدام هذا النهج بواسطة خيارات Run File في IDE. يمكن ملاحظة أنه في هذا السيناريو، تكون القيمة الأولى لـ sys.path هي الدليل الذي يوجد به ملف البرنامج النصي، ويختلف ذلك باختلاف مسار البرنامج النصي. تذكر أن تنفيذ الاختبار لدينا يكون دائمًا في الدليل /home/frostming/test_path.

الآن، إذا كنا بحاجة إلى استيراد a.py إلى b.py، حيث يحتوي a.py على سطر بسيط print("I'm a")، فكيف يجب كتابة b.py؟

سهل! فقط اكتب import a. حسنًا، دعنا نجري الاختبار مرة أخرى كما هو موضح أعلاه.

$ python main.py
ModuleNotFoundError: No module named 'a'
$ python my_package/b.py
I'm a
I'm b
['/home/frostming/test_path/my_package', ...]

فشل الاختبار الأول. إذا كنت قد قرأت المحتوى السابق، فمن المتوقع حدوث هذا الخطأ – لا يتضمن sys.path دليل a.py، وهو /home/frostming/test_path/my_package. بطبيعة الحال، لا يمكنه العثور على a.py.

بتغييره إلى from my_package import a، فلن نجري الاختبار مرة أخرى، لأنه بناءً على نفس التحليل، يمكننا التنبؤ بأن التشغيل الأول سيكون جيدًا، لكن التشغيل الثاني سيؤدي إلى خطأ، غير قادر على العثور على my_package. لاحظ أنه نظرًا لأن b موجود داخل حزمة my_package، فيمكن استخدام الاستيراد النسبي. الكتابة from . import a وfrom my_package import a لها نفس التأثير.

هل توجد طريقة لجعل كلا التشغيلين لا ينتجان أخطاء؟ نعم. نحتاج إلى فهم أنه في المشروع، توجد نقاط دخول محدودة. في الواقع، لن يوجد كود قابل للتنفيذ في المستوى الأعلى وفي الدلائل الفرعية. يجب أن نضع المنطق الرئيسي في main.py (لا يجب أن يكون هذا الاسم؛ على سبيل المثال، في مشروع Django، يمكن أن يكون manage.py). إذا كانت هناك حاجة لتشغيل كود من برنامج نصي في دليل فرعي، فيجب القيام بذلك باستخدام python -m [module_name]. يجب أن تكون عبارة الاستيراد في b.py لاستيراد a من my_package import a. دعنا نلقي نظرة على النتائج:

$ python main.py  # same as python -m main
I'm a
I'm b
['/home/frostming/test_path', ...]
$ python -m my_package.b
I'm a
I'm b
['/home/frostming/test_path', ...]

يمكننا أن نرى أن محتويات sys.path متسقة في كلا التشغيلين. القيمة الأولى هي الدليل الذي يقع فيه التشغيل الحالي. تسمى طريقة التشغيل هذه التشغيل كوحدة نمطية. المعلمة بعد python -m هي اسم الوحدة النمطية (مفصولة بنقاط)، وليس اسم المسار. وبسبب هذا التوحيد، يمكنك استخدام نفس تنسيق الاستيراد لجميع عمليات الاستيراد في مشروعك، بغض النظر عن موقع البرنامج النصي. وهذا هو أيضًا سبب توصية وثائق Django الرسمية باستخدام أسماء مثل myapp.models.users لعمليات الاستيراد.

بالإضافة إلى ذلك، عند التشغيل كوحدة نمطية، يتم تنفيذ كل مستوى من الوحدة النمطية الأساسية (أو الحزمة) المحددة كوحدة نمطية. وهذا يعني أنه يمكنك استخدام الواردات النسبية في الوحدة النمطية (وهو أمر غير مسموح به عند التشغيل مباشرة)، ويتم تعيين قيمة __name__ في الوحدة النمطية المُرسَلة إلى __main__، مما يسمح لك بتطبيق علامة الاختيار if __name__ == "__main__":. إذا كانت الوحدة النمطية المُرسَلة في python -m [module_name] عبارة عن حزمة، فسيتم تنفيذ البرنامج النصي __main__.py في دليل الحزمة (إذا كان موجودًا)، وستكون قيمة __name__ لهذا البرنامج النصي هي __main__.

فيما يتعلق بالبحث عن مسارات الحزمة، فإن العنصر الأكثر أهمية هو البادئة $path_prefix، والتي يتم اشتقاقها من مسار مفسّر بايثون المستخدم. لذلك، للعثور على مسار الحزمة، تحتاج فقط إلى معرفة مسار المفسّر. إذا واجهت موقفًا حيث يتعين تغيير مسار الحزمة، فما عليك سوى تعيين PATH الصحيح، وتحديد مفسّر بايثون الذي تريده.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading