الحزم 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 في توثيق بايثون الرسمي.