دمج حزم متعددة في مساحة اسم Python واحدة

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

إن ورقة الغش هذه ، والتي هي أكثر ملاءمة للمبتدئين ، مخصصة لمساحات أسماء Python.

دعونا نلقي نظرة على كيفية القيام بذلك في إصدارات مختلفة من Python ، لأنه على الرغم من أن Python2 لم يعد مدعومًا قريبًا ، فإن الكثير منا الآن بين نارين من الحرائق ، وهذه مجرد واحدة من الفروق الدقيقة المهمة في عملية الانتقال.

صورة

النظر في هذا المثال:

نريد الحصول على بنية الحزمة:

namespace1 package1 module1 package2 module2 

محتويات ملف Module1

 print('package 1') var1 = 1 

محتويات ملف Module2

 print('package 2') var2 = 2 

في الوقت نفسه ، يتم توزيع الحزم في بنية المجلد التالية:

  path1 namespace1 package1 module1 path2 namespace1 package2 module2 

لنفترض أنه تم بالفعل إضافة المسار 1 و path2 بالفعل إلى sys.path. نحن بحاجة إلى الوصول إلى module1 و module2:

  from namespace1.package1 import module1 from namespace1.package2 import module2 

ماذا يحدث في Python 3.7 عند تنفيذ هذا الرمز؟ كل شيء يعمل بشكل رائع:

 package 1 package 2 

مع PEP-420 في Python 3.3 ، ظهر دعم لمساحات الأسماء الضمنية. بالإضافة إلى ذلك ، عند استيراد حزمة من py33 ، لن تحتاج إلى إنشاء ملفات __init__.py. وعند استيراد مساحة الاسم ، يكون هذا فقط ممنوعًا. إذا كان الملف __init__.py موجودًا في أحد الدلائل أو كلاهما يحمل الاسم name1 ، فسيحدث خطأ عند استيراد الحزمة الثانية.

 ModuleNotFoundError: No module named 'namespace1.package2' 

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

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

 from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

ماذا يحدث مع هذا؟ عندما يصل المترجم إلى الاستيراد الأول ، يتم البحث في الحزمة التي تحمل نفس الاسم في sys.path ، ويكون في path1 / namespace1 وينفذ المترجم path1 / namespace1 / __ init__.py. لا يتم إجراء مزيد من البحث. ومع ذلك ، فإن وظيفة extension_path نفسها تبحث بالفعل عبر sys.path ، وتبحث عن جميع الحزم بمساحة الاسم 1 والاسم الداخلي وتضيفها إلى المتغير __path__ الخاص بحزمة namespace1 ، والذي يتم استخدامه للبحث عن الحزم التابعة في مساحة الاسم هذه.

في الأدلة الرسمية ، يوصى بأن تكون الأحرف الأولى كما هي في كل مرة يتم فيها وضع مساحة الاسم 1. في الواقع ، يمكن أن تكون فارغة جميعها باستثناء الأولى ، والتي يتم العثور عليها أثناء البحث في sys.path ، والتي يجب أن تسمى pkgutil.extend_path ، لأنه لم يتم تنفيذ الباقي. ومع ذلك ، بالطبع ، من الأفضل أن تكون المكالمة في كل مكتب داخلي ، حتى لا تربط منطقك "في حالة" وألا تخمين الفريق الذي كان أول من ينفذ ، لأن ترتيب البحث يمكن أن يتغير. للسبب نفسه ، يجب ألا تضع أي ملفات منطقية أخرى في منطقة المتغيرات.

سيعمل هذا في الإصدارات المستقبلية ، ويمكن استخدام هذا الرمز لكتابة رمز متوافق ، ولكن تذكر أنه يجب عليك الالتزام بالطريقة المختارة في كل حزمة موزعة. إذا وضعت في الإصدار 3 في علبة في بعض الحزم في مكالمة إلى pkgutil.extend_path وتركت بعضها دون علبة الوارد ، فلن ينجح ذلك.
بالإضافة إلى ذلك ، يعد هذا الخيار مناسبًا أيضًا للحالة عندما تخطط لتثبيت باستخدام تثبيت python setup.py.

طريقة أخرى ، والتي تعتبر الآن قديمة إلى حد ما ، ولكن لا يزال من الممكن العثور عليها كثيرًا حيث:

 #namespace1/__init__.py __import__('pkg_resources').declare_namespace(__name__) 

وحدة pkg_resources تأتي مع حزمة setuptools. المعنى هنا هو نفسه كما في pkgutil - من الضروري أن يحتوي كل ملف __init__ على نفس بيان مساحة الاسم في كل موقع من مساحة الاسم 1 ولا يوجد رمز آخر. في الوقت نفسه ، من الضروري تسجيل مساحة الاسم namespace_packages = ['namespace1'] في setup.py. يوجد وصف أكثر تفصيلاً لإنشاء الحزم خارج نطاق هذه المقالة.

بالإضافة إلى ذلك ، يمكنك العثور على مثل هذا الرمز في كثير من الأحيان

 try: __import__('pkg_resources').declare_namespace(__name__) except: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

المنطق هنا بسيط - إذا لم يتم تثبيت setuptools ، فإننا نستخدم pkgutil ، والذي يتم تضمينه في المكتبة القياسية.

إذا قمت بتكوين مساحة اسم بإحدى هذه الطرق ، يمكنك الاتصال بأخرى من وحدة نمطية واحدة. على سبيل المثال ، قم بتغيير مساحة الاسم 1 / package2 / module2

 import namespace1.package1.module1 print(var1) 

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

 namespace1 package1 module1 package1 module2 

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

 from namespace1.package1 import module1 from namespace1.package1 import module2 #>>ImportError: cannot import name module2 

خلاصة القول:

  1. بالنسبة لـ Python الأقدم من 3.3 والتثبيت باستخدام نقطة ، فمن المستحسن أن تستخدم إعلان مساحة اسمية ضمني.
  2. في حالة دعم الإصدارين 2 و 3 ، بالإضافة إلى التثبيت مع تثبيت كل من pip و python setup.py ، يوصى بخيار pkgutil.
  3. يوصى بخيار pkg_resources إذا كنت بحاجة إلى دعم الحزم القديمة باستخدام هذه الطريقة ، أو كنت بحاجة إلى أن تكون الحزمة آمنة من نوع zip.

مصادر:


أمثلة يمكن العثور عليها هنا .

Source: https://habr.com/ru/post/ar458432/


All Articles