ما الغرض من ملف init__.py__ في بايثون؟

يُعرّف ملف __init__.py الخاص بلغة بايثون المجلد كحزمة بايثون عادية، ويتيح لك استيراد وحداته. يُشغّل هذا الملف تلقائيًا عند استيراد الحزمة الحاوية له لأول مرة. يمكنك استخدامه لتهيئة متغيرات مستوى الحزمة، وتعريف الدوال أو الأصناف، وهيكلة مساحة اسم الحزمة بشكل واضح للمستخدمين.

إن فهم كيفية استخدام __init__.py بشكل فعال يساعدك على تنظيم حزم بايثون الخاصة بك بطريقة واضحة وقابلة للصيانة، مما يحسن من سهولة الاستخدام وإدارة مساحة الاسم.

ملاحظة: اسم الملف __init__.py يصعب نطقه. يجد معظم الناس أنه من الأسهل تسميته دندر-إنيت.

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

إذن، ما الغرض من ملف __init__.py؟

باختصار: يُعرّف الملف __init__.py مجلدًا كحزمة بايثون عادية

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

يُتيح وجود ملف __init__.py لمُحمّل بايثون التعرّف على المجلد الذي يحتويه كحزمة عادية. وهذا يعني أنه يُمكنك استيراد المجلد بأكمله، أو وحدات مُحددة داخله، أو حتى دوال ومتغيرات وفئات مُفردة من تلك الوحدات باستخدام اسم الحزمة.

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

كما ذكرنا، قد يكون ملف __init__.py ملفًا فارغًا، وفي هذه الحالة يكون دوره فقط هو تحديد الحزمة.

ماذا يحدث عندما أضيف كودًا إلى ملف __init__.py؟

إذا كان ملف __init__.py يحتوي على كود، فسيتم تنفيذ هذا الكود عند استيراد الحزمة لأول مرة.

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

ستبدأ بمثال بسيط يعتمد على حزمة واحدة. إليك الإعداد:

tools_project/
│
└── tools/
    └── __init__.py

إذن، اسم مشروعك هو tools_project. ويحتوي على حزمة باسم tools. كان من الممكن أن يكون ملف tools/__init__.py فارغًا تمامًا، ولكنه يحتوي هنا على بضعة أسطر من التعليمات البرمجية:

__version__ = "1.0.0"
magic_number = 42

بعد وضع هذه الملفات في مكانها، ابدأ جلسة REPL في دليل tools_project، وشاهد ما يحدث عند استيراد واستخدام حزمة الأدوات:

>>> import tools
>>> tools.__version__
'1.0.0'
>>> tools.magic_number
42

عند تنفيذ أمر استيراد الأدوات في السطر 1، تم تنفيذ ملف __init__.py الموجود في مجلد الأدوات ضمنيًا. هذا جعل أسماء الملفات في tools/__init__.py متاحة عبر البادئة tools. عادةً، يُستخدم ملف __init__.py لتعريف متغيرات خاصة بالحزمة التي يتم استيرادها، مثل إصدار الحزمة أو ثابت عام للحزمة.

ملاحظة: يتم تنفيذ الكود الموجود في ملف __init__.py مرة واحدة فقط، حتى لو قمت باستيراد الحزمة عدة مرات. ستشاهد هذا بمزيد من التفصيل لاحقًا.

في الجزء المتبقي من هذا البرنامج التعليمي، ستتعرف على المزيد حول متى ولماذا وكيف قد ترغب في استخدام __init__.py.

ماذا يحدث إذا لم أضف ملف __init__.py؟

حتى الآن، تعلمتَ عن الحزم العادية، لكن بايثون تدعم أيضًا نوعًا آخر من الحزم، يُعرف باسم حزم النطاقات. لا تحتوي حزمة النطاقات على ملف __init__.py. يمكنك استيرادها، بشرط أن تتمكن بايثون من العثور عليها عبر sys.path. إذا لم تُضِف ملف __init__.py إلى مجلد، فسيتم التعامل معه كحزمة نطاقات.

تُعرّف كل من الحزم العادية وحزم مساحات الأسماء مساحات الأسماء. ويكمن الفرق في أن حزمة مساحة الاسم لا تقتصر على دليل واحد، بل إن بادئة مساحة الاسم، وليس الدليل الفعلي، هي التي تحدد محتويات الحزمة.

ملاحظة: تُستخدم حزم مساحات الأسماء منذ سنوات عديدة، ولكنها كانت تتطلب سابقًا بعض الإعدادات الإضافية. منذ اعتماد PEP 420 في بايثون 3.3، أصبحت اللغة تحتوي على حزم مساحات أسماء ضمنية، يتم تحديدها فقط من خلال غياب ملف __init__.py. هذا يجعل إنشاءها عمدًا أسهل، ولكنه يجعل إنشاءها عن طريق الخطأ أسهل أيضًا!

تذكر أن مساحة الاسم هي اصطلاح يُستخدم لتمييز كائنات برمجية مختلفة قد تتشابه في اسمها الأساسي، سواء كان اسم متغير أو دالة أو فئة أو أي شيء آخر. باستخدام بادئات مختلفة، يمكنك تجنب الالتباس.

على سبيل المثال، في جزء كبير من التعليمات البرمجية، قد تجد وحدتين منفصلتين تحملان اسم “utils”. لنفترض أن إحداهما تحتوي على أدوات مالية، بينما تحتوي الأخرى على أدوات خاصة بالرواتب. بوضعهما في نطاقين مختلفين – يُسميان “finance” و”payroll” – فإنك تسمح لـ Python بالتمييز بينهما، على الرغم من اشتراكهما في نفس الاسم الأساسي. وذلك لأن Python يتعامل مع “finance.utils” و”payroll.utils” ككائنات برمجية منفصلة تمامًا.

يمكنك بسهولة إضافة حزمة مساحة اسم إلى مشروعك التجريبي. ما عليك سوى إضافة دليل جديد فارغ أسفل مجلد tools_project:

tools_project/
│
├── tools/
│   └── __init__.py
│
└── tools_ns/

تذكر أن كلاً من الوحدات والحزم يجب أن تكون معرفات بايثون صالحة. لذا، يحتوي اسم الدليل الجديد على شرطة سفلية (_) بدلاً من، على سبيل المثال، واصلة (-).

الآن، في مجلد tools_project، استخدم واجهة سطر الأوامر التفاعلية (REPL) للتأكد من إمكانية استيراد هذه الحزمة أيضًا. تحتوي كل حزمة بايثون على سمة .__path__ . لاحظ أن  tools_ns.__path__ تبدو مختلفة قليلاً عن .__path__ .

>>> import tools
>>> tools.__path__
['/tools_project/tools']

>>> import tools_ns
>>> tools_ns.__path__
_NamespacePath(['/tools_project/tools_ns'])

يجد بايثون tools_ns في الدليل الحالي، ويلاحظ أنه لا يحتوي على __init__.py  بداخله، ويستورده كحزمة مساحة اسم.

على الرغم من أن tools_ns تم استيرادها بنجاح، إلا أن سمة .__path__  تشير إلى أنها حزمة مساحة اسم، وليست حزمة عادية.

ملاحظة: يحتفظ وقت تشغيل بايثون بقائمة من الدلائل تُسمى sys.path، يستخدمها مبدئيًا لتحميل مكتباته القياسية، ولاحقًا لتحديد أي استيرادات مُعلنة في التعليمات البرمجية. تتضمن القائمة مسارات أي مكتبات قمت بتثبيتها، بالإضافة إلى الدليل الحالي للبرنامج النصي قيد التشغيل.

يمكنك إضافة المزيد من المجلدات إلى sys.path إما بإضافتها إلى متغير البيئة PYTHONPATH، أو بتعديل sys.path أثناء التشغيل. مع ذلك، عادةً ما يكون ذلك غير ضروري، إذ سيُعثر على الكود الذي طورته محليًا في مجلد البرنامج النصي نفسه ومجلداته الفرعية. هكذا يعثر بايثون على tools و tools_ns في هذا المثال.

تُعدّ حزم مساحات الأسماء مفيدة لدمج قواعد بيانات متعددة في بنية تسمية موحدة. على سبيل المثال، قد ترغب شركة لديها عدد من مواقع التطوير في دمج مساهماتها المختلفة تحت مساحة اسم واحدة.

على سبيل المثال، قد تستخدم شركة تُدعى ميجاكورب إنك. نطاق الاسم com.megacorp. وبدلاً من إجبار المستخدمين على تنزيل جميع التعليمات البرمجية داخل نطاق الاسم الخاص بهم في حزمة واحدة، يمكنهم نشر مكتبات أصغر منفصلة تشترك جميعها في نفس البادئة، مثل com.megacorp.finance و com.megacorp.sales.

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

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

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

ستتناول بقية هذه الدورة التعليمية الحزم العادية.

ما نوع الكود الذي يجب أن أضعه في ملف __init__.py؟

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

ومع ذلك، يمكن أن يحتوي ملف __init__.py أيضًا على وظائف وفئات وبيانات وعبارات استيراد مطلقة أو نسبية.

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

يُعد ملف __init__.py  مكانًا جيدًا لتعريف الدوال المساعدة والثوابت والمتغيرات التي تنطبق على الحزمة بأكملها.

باختصار، يمكنك استخدام ملف  __init__.py للقيام بما يلي:

  • قم باستيراد الوحدات النمطية الأخرى التي يحتاجها الكود داخل ملف __init__.py نفسه
  • قم بتعريف المتغيرات أو الدوال على مستوى الحزمة
  • قم بإعداد التكوين أو الإعدادات الخاصة بالحزمة
  • قم بتضمين الوثائق أو تحديد البيانات الوصفية للحزمة
  • توفير مساحة أسماء مبسطة ومتماسكة لمستخدمي الحزم

الآن بعد أن عرفت نوع الكود الذي قد ترغب في تضمينه في  __init__.py، قد تتساءل متى يقوم بايثون بتنفيذ الكود الموجود في هذا الملف.

متى يتم تنفيذ ملف __init__.py؟

يتم تنفيذ ملف __init__.py عند استيراد الحزمة لأول مرة. سيحدث ما يلي:

  • إذا قمت باستيراد الحزمة بشكل صريح
  • إذا قمت باستيراد وحدة نمطية داخل الحزمة
  • إذا قمت باستيراد حزمة فرعية من الحزمة الرئيسية، أو وحدة نمطية داخل تلك الحزمة الفرعية

يبدأ تنفيذ ملف __init__.py  بمجرد أن يصادف المفسر عبارة الاستيراد المقابلة. كما سيتم تنفيذ أي عبارات استيراد متداخلة داخل الملف عند مصادفتها.

كيف يتم استخدام ملف __init__.py في مثال عملي؟

إليك مثال عملي لكيفية استخدام ملف __init__.py لنفترض أنك بدأت مشروعًا جديدًا باسم media_project، وله مجلد رئيسي خاص به. يوجد أسفل هذا المجلد مجلد باسم mediatools يحتوي على ملف __init__.py خاص به، يُعرّف حزمة mediatools ومساحة اسمها. ضمن هذه الحزمة، يمكنك تضمين حزمتين فرعيتين باسم audio و graphics. إليك تخطيط الملفات:

media_project/
│
└── mediatools/
    │
    ├── audio/
    │   ├── __init__.py
    │   └── utils.py
    │
    ├── graphics/
    │   ├── __init__.py
    │   └── utils.py
    │
    └── __init__.py

ضمن مشروع الوسائط، أنشأتَ حزمة mediatools، التي تحتوي على حزمتين فرعيتين: mediatools/audio و mediatools/graphics. تحتوي الوحدة mediatools/audio/utils.py على دوال مساعدة للصوت، وتعريفات للثوابت، وما شابه، بينما تؤدي الوحدة mediatools/graphics/utils.py دورًا مشابهًا لجزء الرسومات في مشروعك.

لاحظ أن لكل حزمة ملفها الخاص __init__.py، لذا فهي حزم عادية. لأغراض هذا المثال، ستقوم بتعبئة وحدة mediatools/audio/utils.py كما يلي:

print(f"Importing {__name__}")

def wobbulate():
    print("Wibble wobble")

def enhance():
    print("Enhancing audio")

_secret_password = "Alakazam!"
__top_secret_password = "!mazakalA"

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

يُطلق على المتغيرين _secret_password و __top_secret_password في السطرين 9 و 10 اسم المتغيرات غير العامة. ولا يُفترض استخدامهما خارج الوحدة النمطية الخاصة بهما. ستتعرف قريبًا على معنى ذلك عمليًا.

ستضيف أيضًا بعض التعليمات البرمجية إلى ملف mediatools/graphics/utils.py:

print(f"Importing {__name__}")

def solarize():
    print("Solarizing")

def enhance():
    print("Enhancing graphics")

indiana_pi = 3.2

لذا فإن حزم الصوت والرسومات تحتوي كل منها على وحدة تسمى utils.py، وتحتوي كل منها على استدعاء تصحيح الأخطاء لـ ()print في الأعلى والذي سيعرض اسم الوحدة وبعض تعريفات الوظائف وتصريحات المتغيرات.

على سبيل المثال، تحتوي وحدة audio.utils على الدالة ()wobbulate. إذا كان لديك برنامج رئيسي في دليل media_project، فيمكنه استدعاء تلك الدالة بطرق مختلفة، مثل استيراد الوحدة مع بادئة مساحة الاسم الكاملة الخاصة بها:

import mediatools.audio.utils

mediatools.audio.utils.wobbulate()

أو عن طريق استيراد اسم الوحدة فقط:

from mediatools.audio import utils

utils.wobbulate()

أو عن طريق استيراد اسم الدالة بدون أي بادئة على الإطلاق:

from mediatools.audio.utils import wobbulate

wobbulate()

بعد ذلك، أضف استدعاءك التجريبي للدالة ()print إلى ملف mediatools/__init__.py، حتى تعرف متى يتم تنفيذه:

print(f"Importing {__name__}")

أضف نفس السطر إلى ملف graphics/__init__.py:

print(f"Importing {__name__}")

وأيضًا إلى ملف audio/__init__.py:

print(f"Importing {__name__}")

مع إعداد استدعاءات التصحيح هذه للدالة ()print، ستتمكن من مراقبة وقت تنفيذ الكود في كل ملف __init__.py. يحدث هذا عند استيراد الحزمة الخاصة به. يمكنك رؤية ذلك عمليًا عن طريق بدء جلسة REPL جديدة.

>>> from mediatools.audio import utils as audio_utils
Importing mediatools
Importing mediatools.audio
Importing mediatools.audio.utils

>>> from mediatools.graphics import utils as graphics_utils
Importing mediatools.graphics
Importing mediatools.graphics.utils

>>> audio_utils.wobbulate()
Wibble wobble

>>> graphics_utils.solarize()
Solarizing

>>> graphics_utils.indiana_pi
3.2

في هذا الكود، اخترتَ البادئة audio_utils للإشارة إلى أن الدالة تأتي من وحدة utils ضمن حزمة الصوت. وبالمثل، تُشير البادئة graphics_utils إلى دالة من وحدة utils ضمن حزمة الرسومات.

على الرغم من أن عبارات الاستيراد الخاصة بك ذكرت صراحةً وحدتي utils فقط، إلا أن استدعاءات دالة ()print تُظهر أن الكود الموجود في الملفات mediatools.init.py و mediatools.audio.init.py و mediatools.graphics.init.py قد نُفِّذ ضمنيًا. لذا، فإن الأسطر الثلاثة الأولى التي طُبعت على وحدة التحكم بعد استيراد وحدة utils من mediatools.audio تأتي من الملفات التالية:

  • يطبع الملف mediatools/__init__.py عبارة Importing mediatools”
  • يطبع الملف mediatools/audio/__init__.py عملية Importing mediatools.audio
  • يطبع الملف mediatools/audio/utils.py ما يلي: Importing mediatools.audio.utils

لاحظ أن الكود الموجود في ملف mediatools.__init__.py قد تم تنفيذه مرة واحدة فقط، على الرغم من أن mediatools هي حزمة رئيسية لكلتا الوحدتين المستوردتين. ستعرف سبب حدوث ذلك بعد قليل.

لقد تأكدتَ من أن الكود الموجود في ملف __init__.py يُنفَّذ تلقائيًا عند استيراد الحزمة، أو أي عنصر بداخلها. مع أن هذا الكود قادر على القيام بأي شيء تقريبًا، إلا أن غرضه المعتاد هو تهيئة الحزمة وتحديد أي سمات أو دوال أو بيانات عامة خاصة بها. وقد يقوم أيضًا بعمليات استيراد، إما لاستخدامه الداخلي أو لعرض الأسماء المستوردة على كود العميل.

يحتفظ مُفسِّر بايثون بذاكرة تخزين مؤقتة للوحدات المُستوردة، لذا لا يُمكنك استيراد الوحدة نفسها مرتين باستخدام عبارة استيراد عادية. يتجاهل بايثون عبارة الاستيراد الثانية تلقائيًا، ولن يُعاد تنفيذ محتويات الملف __init__.py إذا استوردتَ حزمتين فرعيتين من الحزمة الأصلية نفسها، فسيتم استيراد الحزمة الأصلية وكل حزمة فرعية مرة واحدة فقط.

انظر ماذا يحدث عندما تبدأ جلسة REPL جديدة وتحاول تحميل ملف audio.utils مرتين:

>>> from mediatools.audio import utils
Importing mediatools
Importing mediatools.audio
Importing mediatools.audio.utils

>>> from mediatools.audio import utils as more_utils

>>> utils.enhance()
Enhancing audio

>>> more_utils.enhance()
Enhancing audio

لم يقم بيان الاستيراد الثاني بتنفيذ أي عمليات استيراد – لقد قام فقط بتسمية أدوات الاستيراد السابقة باسم more_utils.

قبل المتابعة، يمكنك إزالة استدعاءات تصحيح الأخطاء لـ ()print من جميع ملفاتك لتجنب تشويش الإخراج في الأقسام التالية.

هل يمكنني استخدام ملف __init__.py للتحكم في ما يتم تصديره من حزمة؟

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

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

>>> import mediatools.audio.utils

>>> mediatools.audio.utils._secret_password
'Alakazam!'

>>> mediatools.audio.utils.__top_secret_password
'!mazakalA'

يسعد بايثون باستيراد وطباعة قيم هذه المتغيرات التي يفترض أنها غير عامة.

يلتزم بناء جملة الاستيراد * بقاعدة الشرطة السفلية. يمكنك استخدام هذا البناء لاستيراد محتويات الوحدة النمطية mediatools/audio/utils.py في جلسة REPL جديدة.

>>> from mediatools.audio.utils import *

>>> _secret_password
Traceback (most recent call last):
  ...
NameError: name '_secret_password' is not defined

>>> __top_secret_password
Traceback (most recent call last):
  ...
NameError: name '__top_secret_password' is not defined

باستخدام هذه الصيغة، تصبح معظم الأسماء الموجودة داخل mediatools.audio.utils متاحة لبرنامجك بدون البادئة، ولكن لا يتم استيراد متغيرات الشرطة السفلية.

ملاحظة: غالبًا ما يكون استخدام صيغة import * مناسبًا في العمل التفاعلي، ولكنه يُعتبر ممارسة سيئة في المشاريع الكبيرة لأنه يمحو معلومات مساحة اسم الوحدة. يتم استيراد جميع الكائنات المُعرّفة في mediatools.audio.utils – باستثناء تلك التي تبدأ بشرطة سفلية – وتفقد بادئتها في هذه العملية. تصبح هذه الكائنات جزءًا من مساحة الاسم العامة، مما يزيد من خطر تضارب الأسماء.

لرؤية هذه المشكلة عمليًا، ابدأ جلسة REPL جديدة في دليل media_project واستخدم import * للوحدات الفرعية الخاصة بك:

>>> from mediatools.audio.utils import *

>>> from mediatools.graphics.utils import *

>>> wobbulate()
Wibble wobble

>>> indiana_pi
3.2

>>> solarize()
Solarizing

>>> enhance()
Enhancing graphics

يمكنك توضيح استخدام __all__ عن طريق إضافة سطر إلى mediatools/graphics/utils.py:

# ...

__all__ = ["solarize", "enhance"]

مع هذه الإضافة، سيقوم الأمر import * باستيراد الدالتين المذكورتين فقط، ولن يكون المتغير indiana_pi مرئيًا بعد الآن:

>>> from mediatools.graphics.utils import *

>>> indiana_pi
Traceback (most recent call last):
  ...
NameError: name 'indiana_pi' is not defined

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

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

from .audio.utils import enhance as audio_enhance
from .audio.utils import wobbulate
from .graphics.utils import enhance as graphics_enhance
from .graphics.utils import solarize

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

الآن يمكن لكود المستخدم الاستفادة من الواجهة الجديدة:

>>> from mediatools import (
...     wobbulate,
...     solarize,
...     audio_enhance,
...     graphics_enhance
... )

>>> wobbulate()
Wibble wobble

>>> solarize()
Solarizing

>>> audio_enhance()
Enhancing audio

>>> graphics_enhance()
Enhancing graphics

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

في بعض الأحيان، قد ترغب في استيراد الحزم إلى __init__.py لاستخدامها الداخلي، ولكن لتجنب تلويث مساحة الاسم، لا تريد الكشف عن تلك الأسماء لمستخدمي حزمتك.

إليك ملف  __init__.py تخطيطي يقوم بذلك لتطبيق افتراضي:

from datetime import date as _date
from math import gcd as _gcd
from some.thirdparty.dateutils import lunar_month as _lunar_month

def fancy_date_calculator() -> str:
    today = _date.today
    # Some fancy calculations using _gcd and _lunar_month
    ...
    return "Calculated result"

عندما يستورد كود المستخدم هذه الحزمة، سيرى دالة عامة واحدة فقط: ()fancy_date_calculator. أما باقي الرموز فستُسبق بشرطات سفلية، مما يُوضح أنها غير مُخصصة للاستخدام العام.

هل يختلف ملف  __init__.py الموجود في المستوى الأعلى عن الملف الموجود في حزمة فرعية؟

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

على سبيل المثال، لنفترض أنك تريد إضافة رقم إصدار حزمة. سيكون المكان المناسب لذلك داخل ملف mediatools/__init__.py:

__version__ = "0.1.0"

# ...

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

في هذا البرنامج التعليمي، تعلمت عن الدور الخاص لملف __init__.py الخاص بلغة بايثون في تعريف الحزم العادية وتمييزها عن حزم مساحة الاسم.

لقد استكشفتَ كيفية تنفيذ بايثون للتعليمات البرمجية داخل ملف __init__.py  عند استيراد الحزمة، وكيفية استخدامها لهيكلة مساحات الأسماء وواجهات برمجة التطبيقات، وكيفية التعامل مع عمليات الاستيراد بكفاءة. بالإضافة إلى ذلك، تعرّفتَ على اصطلاحات تسمية الرموز غير العامة باستخدام الشرطة السفلية في بايثون، وكيف يُمكن للمتغير  __all__ أن يُساعد في تحديد الواجهة العامة للوحدة النمطية بشكلٍ صريح.

يُساعدك الاستخدام الفعال لملف __init__.py على إنشاء حزم برمجية منظمة وسهلة الصيانة وبديهية. فمن خلال تنظيم حزمك بشكل صحيح، تُحسّن من سهولة قراءتها، وتُشجع على إعادة استخدامها، وتُبسط عملية تهيئة الحزمة.


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

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

اترك تعليقاً

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

Scroll to Top

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

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

Continue reading