الحزم Packages في بايثون

من موسوعة حسوب

تعدّ الحزم طريقة لهيكلة مجالات أسماء الحزم في بايثون، وذلك باستخدام أسماء الحزم المنقطة (dotted module names). فعلى سبيل المثال يعبّر اسم الحزمة A.B عن أن الحزمة B هي جزء من الحزمة A.

وكما أنّ الوحدات تساعد في تجنّب حدوث أي تضارب بين أسماء المتغيرات العامة، فإنّ أسماء الحزم المنقطة تساعد في تجنب حدوث أي تضارب بين أسماء الوحدات في الحزم متعدّدة الوحدات مثل NumPy أو مكتبة بايثون لمعالجة الصور Python Imaging Library.

لنفترض أنّك ترغب في تصميم مجموعة من الوحدات (حزمة) لمعالجة ملفات الصوت والبيانات الصوتية. هناك العديد من الصيغ الخاصة بالملفات الصوتية (عادة يمكن التعرف عليها من خلال اللاحقة، مثل: ‎.wav، ‎.aiff، ‎.au)؛ لذا قد تحتاج إلى تطوير مجموعة من الحزم التي تقوم بعملية التحويل بين صيغ الملفات الصوتية المختلفة. هناك أيضًا العديد من العمليات المختلفة التي يمكن أن تؤدِّيها على البيانات الصوتية (مثل المزج وإضافة الصدى وتطبيق دالة تسوية [equalizer function]، أو إضافة مؤثّرات ستيريو صناعية). وهكذا يزداد عدد الوحدات شيئًا فشيئًا بازدياد عدد العمليات التي ترغب في إضافتها إلى هذه الحزمة.

يمكن تمثيل هذه الحزمة باستخدام التدرج الهرمي لملفات النظام:

sound/                          الحزمة الرئيسية
      __init__.py               sound تهيئة الحزمة
      formats/                  حزمة فرعية للتحويل بين صيغ الملفات المختلفة
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  حزمة فرعية لإضافة المؤثّرات الصوتية
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  حزمة فرعية للمرشّحات
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

عند استيراد الحزمة تبحث بايثون خلال المجلّدات المعرّفة في المتغيّر sys.path عن المجلد الفرعي للحزمة.

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

يمكن لملف ‎__init__.py في أبسط الأحوال أن يكون فارغًا، ولكن يمكن بواسطة هذا الملف إجراء عملية تهيئة الشيفرة للحزمة، أو تعيين قيمة للمتغير __all__.

يمكن للمستخدم استيراد عدد غير محدّدٍ من الوحدات من الحزمة، فعلى سبيل المثال:

import sound.effects.echo

تستورد الشيفرة السابقة الوحدة الفرعية sound.effects.echo، ومن الجدير بالذكر أنّه لغرض استيراد الوحدة يجب الإشارة إلى اسمها الكامل.

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

هناك طريقة أخرى لاستيراد الوحدة الفرعية:

from sound.effects import echo

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

echo.echofilter(input, output, delay=0.7, atten=4)

يمكن أيضًا استيراد الدالة أو المتغيّر المطلوب بصورة مباشرة:

from sound.effects.echo import echofilter

مرة أخرى تحمّل الشيفرة السابقة الوحدة الفرعية echo، ولكن مع إمكانية الوصول المباشر إلى الدالة echofilter()‎:

echofilter(input, output, delay=0.7, atten=4)

لاحظ أنّه عند استخدام عبارة from package import item، يمكن أن يكون item وحدة فرعية (أو حزمة فرعية) لحزمة معيّنة، أو اسمًا آخر معرّفًا ضمن الحزمة كأن يكون دالة أو صنفًا أو متغيّرًا. 

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

ولكن على العكس ممّا سبق، فعند استخدام صيغة مثل import item.subitem.subsubitem، فإنّ كل العناصر باستثناء العنصر الأخير يجب أن تكون حزمًا، ويمكن للعنصر الأخير أن يكون وحدة أو حزمة ولكن لا يمكن أن يكون صنفًا أو دالة أو متغيّرا معرّفًا في العنصر السابق له.

استيراد جميع مكوّنات الحزمة

ما الذي سيحدث عندما يكتب المستخدم العبارة التالية:

from sound.effects import *

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

الحل الوحيد لهذه المشكلة هو أن يقدّم صاحب الحزمة فهرسًا واضحًا للحزمة.

تتبع عبارة import الأسلوب التالي: إن تضمّن ملف ‎__init__.py الخاص بالحزمة شيفرة تعرّف قائمة باسم __all__، فإنّ عبارة import ستعتمدها كقائمة بأسماء الحزمة التي يجب استيراد عند استخدام عبارة from package import *‎، وتقع مسؤولية تحديث هذه القائمة على عاتق صاحب الحزمة عند إطلاق إصدار جديد منها.

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

__all__ = ["echo", "surround", "reverse"]

هذا يعني أنّ عبارة from sound.effects import *‎ ستستورد الوحدات الفرعية المدرجة في القائمة من حزمة sound.

إن لم تعرّف القائمة __all__ فإنّ العبارة from sound.effects import *‎ لا تستورد جميع الحزم الفرعية من الحزمة sound.effects إلى مجال الأسماء الحالي، ولكنّها تضمن فقط أنّ الحزمة sound.effects قد استوردت (قد يؤدي ذلك إلى تشغيل شيفرة التهيئة في الملف ‎__init__.py) ثم تستورد جميع الأسماء المعرّفة في الحزمة، ويتضمن ذلك جميع الأسماء المعرفة (والحزم الفرعية المحمّلة بصورة صريحة) في الملف ‎__init__.py إضافة إلى أيّ وحدة فرعية تابعة للحزمة والتي جرى تحميلها بصورة صريحة عن طريق عبارات import سابقة.

لاحظ الشيفرة التالية:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

في هذا المثال، تستورد وحدتا echo و surround إلى مجال الأسماء الحالي لأنّهما معرّفتان في الحزمة sound.effects عند تنفيذ عبارة from...import (يحدث الأمر ذاته عند تعريف القائمة __all__).

صحيح أن بعض الوحدات مصمّمة لتصدير الأسماء التي تتبع أنماطًا معيّنة عند استخدام العبارة import *‎، ولكن لا ينصح باعتماد هذا الأسلوب في بيئة الإنتاج (production).

تذكّر أنّه ما من مشكلة في استخدام العبارة:

from Package import specific_submodule

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

الإشارات الداخلية في الحزم Intra-package References

إذا تضمنت الحزم حزمًا فرعية (كما هو الحال في حزمة sound) فبالإمكان استخدام عبارات import مطلقة (absolute) للإشارة إلى الوحدات الفرعية التابعة للحزم الفرعية.

فمثلًا إن احتاجت الوحدة sound.filters.vocoder إلى استخدام الوحدة echo في الحزمة sound.effects فيمكن استخدام عبارة:

from sound.effects import echo

يمكن كذلك استخدام عبارات import نسبية باستخدام الصيغة from module import name. تستخدم عبارات import هذه النقاط في بداية العبارة للإشارة إلى الحزمة الحالية والحزمة الأب المشترِك في عملية الاستيراد النسبية. يمكن استخدام الشيفرة التالية من وحدة surround:

from . import echo
from .. import formats
from ..filters import equalizer

لاحظ أنّ عبارات الاستيراد النسبية مستندة إلى اسم الوحدة الحالية، ولما كان اسم الوحدة الرئيسية هو __main__ دائمًا، يجب استخدام عبارات الاستيراد المطلقة مع الوحدات التي تكون معدّة للاستخدام كوحدات رئيسية في تطبيق Python.

الحزم الموجودة في مجلدات متعدّدة

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

هذا المتغيّر قابل للتعديل، ويؤدي ذلك إلى التأثير على عمليات البحث المستقبلية عن الوحدات والحزم الفرعية الموجودة في الحزمة.

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

مصادر

  • صفحة Modules في توثيق بايثون الرسمي.