AzLearn

الحزم

Packages

مفهوم ~18 دقيقة

الحزم — Packages

تعلّمت في الدرس السابق أن الوحدة هي ملف .py. لكن ماذا حين يكبر المشروع وتصبح لديك عشرات الوحدات؟ هنا تدخل الحزمة (Package) — وهي ببساطة مجلد يحتوي وحدات Python، مع ملف خاص يُعرّف المجلد كحزمة.

المجلد وحده لا يكفي — __init__.py

في Python 2 وحتى جزء كبير من Python 3، كنت تحتاج ملفاً اسمه __init__.py داخل أي مجلد تريده حزمة. هذا الملف يمكن أن يكون فارغاً تماماً وسيؤدي مهمته — إخبار Python أن هذا المجلد حزمة قابلة للاستيراد، لا مجرد مجلد عشوائي في نظام الملفات.

عندما تكتب from collections import Counter، أنت تستورد الاسم Counter من حزمة (أو وحدة) تسمى collections وهي جزء من المكتبة القياسية. كل هذا مبني على نفس فكرة التنظيم الهرمي.

هيكل الحزمة الأساسي

تخيّل مشروعاً يحسب إحصائيات:

stats_project/
├── main.py                  # نقطة الدخول — entry point
└── statslib/                # الحزمة الرئيسية — main package
    ├── __init__.py          # يجعل المجلد حزمة — makes it a package
    ├── arithmetic.py        # عمليات الجمع والطرح — arithmetic ops
    └── analysis.py          # التحليل الإحصائي — statistical analysis

في arithmetic.py:

# statslib/arithmetic.py

def add(a, b):
    """جمع عددين — Add two numbers."""
    return a + b

def subtract(a, b):
    """طرح عددين — Subtract two numbers."""
    return a - b

في analysis.py:

# statslib/analysis.py

def mean(values):
    """المتوسط الحسابي — Arithmetic mean."""
    return sum(values) / len(values)

في main.py يمكنك الاستيراد بطرق عدة:

# استيراد الوحدة كاملة — import full module
import statslib.arithmetic

result = statslib.arithmetic.add(3, 4)  # 7

# استيراد دوال محددة — import specific functions
from statslib.arithmetic import add, subtract
from statslib.analysis import mean

print(add(10, 5))      # 15
print(mean([1, 2, 3])) # 2.0

__init__.py — أكثر من مجرد علامة

رغم أن __init__.py يمكن أن يكون فارغاً، إلا أنه غالباً يُستخدم لتحديد واجهة الحزمة العامة. عندما تكتب from statslib import add، Python تنفّذ __init__.py أولاً. إذا أضفت فيه:

# statslib/__init__.py

# نُعيد تصدير ما نريد المستخدم أن يراه مباشرة — re-export the public API
from statslib.arithmetic import add, subtract
from statslib.analysis import mean

يصبح بإمكان مستخدم حزمتك كتابة:

from statslib import add, mean  # مباشرة، دون معرفة البنية الداخلية

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

الحزم المتداخلة — Nested Packages

الحزم يمكن أن تحتوي حزماً أخرى. المكتبة القياسية لـ Python نفسها مبنية هكذا:

email/                     # حزمة email
    __init__.py
    message.py
    mime/                  # حزمة فرعية — sub-package
        __init__.py
        text.py
        multipart.py

في مشروعك الخاص:

myapp/
├── __init__.py
├── api/
│   ├── __init__.py
│   ├── routes.py
│   └── validators.py
├── db/
│   ├── __init__.py
│   ├── models.py
│   └── queries.py
└── utils/
    ├── __init__.py
    └── formatting.py

الاستيراد يتبع المسار بنقاط:

from myapp.api.validators import validate_email
from myapp.db.queries import get_user_by_id
from myapp.utils.formatting import format_currency

الاستيراد المطلق والنسبي

الاستيراد المطلق (Absolute Import): يبدأ من جذر المشروع. هو الأسلوب المفضّل والأوضح:

# داخل myapp/api/routes.py — absolute imports
from myapp.db.queries import get_user_by_id   # مسار كامل من الجذر
from myapp.utils.formatting import format_currency

الاستيراد النسبي (Relative Import): يبدأ من موقع الملف الحالي بنقطة:

# داخل myapp/api/routes.py — relative imports
from ..db.queries import get_user_by_id      # نقطتان = الحزمة الأم (myapp)
from ..utils.formatting import format_currency
from .validators import validate_email        # نقطة واحدة = نفس الحزمة (api)

نقطة واحدة تعني “نفس المجلد (الحزمة)"، ونقطتان تعنيان “المجلد الأعلى”، وثلاث نقاط تعنيان “اثنتان للأعلى”، وهكذا.

متى تستخدم كل أسلوب؟

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

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

حزم مساحة الأسماء — Namespace Packages

منذ Python 3.3 (PEP 420)، تدعم Python حزم مساحة الأسماء — مجلدات بدون __init__.py. هذه مفيدة حين تكون حزمتك موزعة على حزم pip منفصلة تشترك في بادئة اسم واحدة:

# شركة تنشر: mycompany.auth و mycompany.payments كحزم pip منفصلة
mycompany/           # لا __init__.py هنا!
    auth/
        __init__.py
    payments/
        __init__.py

في الكود العملي اليومي، لا تحتاج لهذه الحالة كثيراً. الأهم أن تعرف: إضافة __init__.py هو الأسلوب الصريح والمفضّل ما لم يكن لديك سبب محدد لحزمة مساحة أسماء.

المكتبة القياسية كمثال حي

الاستخدام الفعلي لحزم Python يراه كل مطور يومياً عبر المكتبة القياسية. collections.Counter هي دالة في وحدة counter.py داخل حزمة collections. دعنا نرى هذا في العمل:

main.go

تحدي الحزم

في هذا التحدي، تخيّل أنك تنظّم حزمة toolkit. أجب عن أسئلة التنظيم بإرجاع القيم الصحيحة:

تحدي — Challenge