6.4. الحزم #
تُعدّ الحزم وسيلةً لتنظيم مساحة أسماء وحدات بايثون باستخدام “أسماء وحدات منقطة”. على سبيل المثال، يُشير اسم الوحدة A.B إلى وحدة فرعية باسم B في حزمة باسم A. وكما يُجنّب استخدام الوحدات مُطوّري الوحدات المختلفة عناءَ القلق بشأن أسماء المتغيرات العامة لبعضهم البعض، فإن استخدام أسماء الوحدات المنقطة يُجنّب مُطوّري الحزم متعددة الوحدات مثل NumPy أو Pillow عناءَ القلق بشأن أسماء وحدات بعضهم البعض.
لنفترض أنك تُريد تصميم مجموعة من الوحدات النمطية (“حزمة”) للتعامل الموحد مع ملفات الصوت وبياناتها. هناك العديد من تنسيقات ملفات الصوت المختلفة (عادةً ما تُعرف بامتداداتها، على سبيل المثال: .wav، .aiff، .au)، لذا قد تحتاج إلى إنشاء مجموعة متزايدة من الوحدات النمطية وصيانتها للتحويل بين تنسيقات الملفات المختلفة. هناك أيضًا العديد من العمليات المختلفة التي قد ترغب في إجرائها على بيانات الصوت (مثل المزج، وإضافة الصدى، وتطبيق دالة معادل الصوت، وإنشاء تأثير ستيريو اصطناعي)، لذا ستكتب أيضًا سلسلة لا تنتهي من الوحدات النمطية لإجراء هذه العمليات. فيما يلي هيكل محتمل لحزمتك (مُعبَّر عنه بنظام ملفات هرمي):
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
عند استيراد الحزمة، يبحث بايثون في المجلدات الموجودة في sys.path عن المجلد الفرعي للحزمة.
ملفات __init__.py مطلوبة لجعل بايثون يُعامل المجلدات التي تحتوي على الملف كحزم. يمنع هذا المجلدات ذات الأسماء الشائعة، مثل السلسلة النصية، من إخفاء الوحدات النمطية الصالحة التي تظهر لاحقًا في مسار بحث الوحدة النمطية عن غير قصد. في أبسط الحالات، يمكن أن يكون ملف __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، يمكن أن يكون العنصر إما وحدة فرعية (أو حزمة فرعية) من الحزمة، أو اسمًا آخر مُعرّفًا في الحزمة، مثل دالة أو فئة أو متغير. تختبر عبارة الاستيراد أولًا ما إذا كان العنصر مُعرّفًا في الحزمة؛ إذا لم يكن كذلك، يفترض أنه وحدة نمطية ويحاول تحميلها. إذا فشل في العثور عليها، يُطلق استثناء ImportError.
على العكس، عند استخدام صيغة مثل import item.subitem.subsubitem، يجب أن يكون كل عنصر باستثناء العنصر الأخير حزمة؛ يمكن أن يكون العنصر الأخير وحدة نمطية أو حزمة، ولكن لا يمكن أن يكون فئة أو دالة أو متغيرًا مُعرّفًا في العنصر السابق.
6.4.1. استيراد * من حزمة #
ماذا يحدث الآن عندما يكتب المستخدم من sound.effects import *؟ من الناحية المثالية، نأمل أن يصل هذا بطريقة ما إلى نظام الملفات، ويبحث عن الوحدات النمطية الفرعية الموجودة في الحزمة، ويستوردها جميعًا. قد يستغرق هذا وقتًا طويلاً، وقد يكون لاستيراد الوحدات النمطية الفرعية آثار جانبية غير مرغوب فيها، والتي يجب أن تحدث فقط عند استيراد الوحدة النمطية الفرعية صراحةً.
الحل الوحيد هو أن يوفر مؤلف الحزمة فهرسًا صريحًا للحزمة. تستخدم عبارة الاستيراد الاصطلاح التالي: إذا عرّفت شيفرة ملف __init__.py للحزمة قائمة باسم __all__، فسيتم اعتبارها قائمة أسماء الوحدات النمطية التي يجب استيرادها عند مواجهة الأمر from package import *. يعود الأمر إلى مؤلف الحزمة في تحديث هذه القائمة باستمرار عند إصدار إصدار جديد من الحزمة. قد يقرر مؤلفو الحزمة أيضًا عدم دعمها إذا لم يروا فائدة من استيراد * من حزمتهم. على سبيل المثال، قد يحتوي الملف sound/effects/__init__.py على الشيفرة التالية:
__all__ = ["echo", "surround", "reverse"]
هذا يعني أن الأمر from sound.effects import * سيستورد الوحدات النمطية الفرعية الثلاث المسماة لحزمة sound.
إذا لم يتم تعريف الأمر __all__، فإن الأمر from sound.effects import * لا يستورد جميع الوحدات النمطية الفرعية من الحزمة sound.effects إلى مساحة الاسم الحالية؛ إنه يضمن فقط استيراد حزمة sound.effects (ربما بتشغيل أي كود تهيئة في __init__.py)، ثم يستورد أي أسماء مُعرّفة في الحزمة. يشمل ذلك أي أسماء مُعرّفة (ووحدات فرعية مُحمّلة صراحةً) بواسطة __init__.py. كما يشمل أي وحدات فرعية من الحزمة t
تم تحميلها صراحةً بواسطة عبارات الاستيراد السابقة. انظر إلى هذا الكود:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
في هذا المثال، يتم استيراد وحدتي echo وsurround في مساحة الاسم الحالية لأنهما مُعرّفتان في الحزمة sound.effects عند تنفيذ عبارة from…import. (يعمل هذا أيضًا عند تعريف __all__).
على الرغم من أن بعض الوحدات مصممة لتصدير الأسماء التي تتبع أنماطًا معينة فقط عند استخدام import *، إلا أنها لا تزال تُعتبر ممارسة سيئة في الكود الإنتاجي.
تذكر، لا حرج في استخدام from package import specific_submodule! في الواقع، هذا هو الترميز المُوصى به إلا إذا احتاجت وحدة الاستيراد إلى استخدام وحدات فرعية بنفس الاسم من حزم مختلفة.
6.4.2. المراجع داخل الحزمة #
عند هيكلة الحزم في حزم فرعية (كما هو الحال مع الحزمة sound في المثال)، يمكنك استخدام الاستيراد المطلق للإشارة إلى الوحدات الفرعية للحزم الشقيقة. على سبيل المثال، إذا احتاجت وحدة sound.filters.vocoder إلى استخدام وحدة echo في حزمة sound.effects، فيمكنها استخدام الأمر from sound.effects import echo.
يمكنك أيضًا كتابة عمليات استيراد نسبية، باستخدام صيغة from module import name لعبارة import. تستخدم هذه الاستيرادات نقاطًا أمامية للإشارة إلى الحزم الحالية والأصلية المشاركة في الاستيراد النسبي. على سبيل المثال، من وحدة surrounding، يمكنك استخدام:
from . import echo
from .. import formats
from ..filters import equalizer
لاحظ أن عمليات الاستيراد النسبية تعتمد على اسم الوحدة الحالية. بما أن اسم الوحدة الرئيسية هو دائمًا “__main__”، يجب على الوحدات المخصصة للاستخدام كوحدة رئيسية لتطبيق بايثون استخدام عمليات استيراد مطلقة دائمًا.
6.4.3. الحزم في مجلدات متعددة #
تدعم الحزم سمة خاصة أخرى، وهي __path__. تتم تهيئتها لتكون قائمة تحتوي على اسم المجلد الذي يحتوي على ملف __init__.py الخاص بالحزمة قبل تنفيذ الكود في ذلك الملف. يمكن تعديل هذا المتغير؛ يؤثر ذلك على عمليات البحث المستقبلية عن الوحدات والحزم الفرعية المضمنة في الحزمة.
على الرغم من أن هذه الميزة ليست ضرورية في كثير من الأحيان، إلا أنه يمكن استخدامها لتوسيع مجموعة الوحدات الموجودة في الحزمة.