الوحدات والحزم في Python: import وpip وvenv والمكتبات بكلمات بسيطة
الوحدة في Python ملف .py، والحزمة مجلد يضم وحدات، والمكتبة مجموعة جاهزة من الحزم، ويُستدعى كل ذلك بالأمر import. تُثبَّت المكتبات الخارجية بـ pip أو uv داخل بيئة افتراضية venv حتى لا تتعارض مشاريع مختلفة، ومنذ Python 3.15 تتيح الكلمة lazy تأجيل تحميل الوحدات الثقيلة حتى أول استخدام.
الوحدة في Python ملف فيه كود، والأمر import وسيلة لأخذ الدوال والأصناف منه. تُجمع الوحدات في حزم، والحزم في مكتبات، ولكي لا تتداخل مكتبات المشاريع المختلفة تُثبَّت في بيئة افتراضية اسمها venv. وفيما يلي شرح السلسلة كاملة: ماذا يحدث عند الاستيراد، وكيف تُبنى الحزمة، وكيف تعمل pip وvenv في Python، وما هو الاستيراد الكسول الذي ظهر في الإصدار 3.15.
الوحدة والحزمة والمكتبة: الفرق بينها
| المفهوم | ما هو | مثال |
|---|---|---|
| الوحدة (module) | ملف واحد .py؛ اسم الوحدة هو اسم الملف بلا امتداد | categories.py ← import categories |
| الحزمة (package) | مجلد يضم وحدات وملف __init__.py | expenses/report.py ← from expenses.report import build |
| المكتبة (library) | مجموعة جاهزة من الوحدات والحزم لمهمة معينة | requests، pandas، والمكتبة القياسية pathlib |
| التوزيعة (حزمة PyPI) | أرشيف يثبّته pip | pip install requests |
تُخلط كلمات «مكتبة» و«حزمة» و«وحدة» في الكلام المعتاد، وهذا طبيعي. المهم أن نعرف أن import يحمّل دائماً وحدة أو حزمة، وأن pip يثبّت توزيعة من PyPI تحتوي على حزم.
كيف يعمل import في Python
توجد أشكال متعددة للاستيراد، وكلها تظهر في الكود الحقيقي:
import math # الوحدة كاملة، والاستخدام math.sqrt
from math import sqrt, pi # أسماء محددة
import datetime as dt # اسم مستعار
from collections import Counter as C
print(math.sqrt(16), sqrt(9), pi)
print(dt.date.today())
الأسماء المستعارة شائعة للمكتبات الكبيرة: import numpy as np وimport pandas as pd. أما الصيغة from module import * فيُفضَّل تجنبها، لأنها تملأ فضاء الأسماء وتخفي مصدر الاسم.
ماذا يفعل import فعلاً
- يبحث عن الوحدة في ذاكرة التخزين المؤقت
sys.modules. إذا كانت محمّلة من قبل يعيدها فقط. - إن لم تكن محمّلة يبحث عن الملف في قائمة المجلدات
sys.path: مجلد السكريبت الذي يعمل، ثم المكتبة القياسية، ثم site-packages الخاص بالبيئة. - ينفّذ الملف الذي وجده كاملاً من الأعلى إلى الأسفل.
- يحفظ كائن الوحدة في الذاكرة المؤقتة ويربطه بالاسم.
وتترتب على ذلك نتيجتان عمليتان. الاستيراد المكرر للوحدة نفسها يكاد يكون بلا كلفة، لأن الكود لا يُنفَّذ مرة ثانية. وكل الكود في المستوى الأعلى للوحدة، بما فيه print والحسابات الثقيلة، يعمل عند الاستيراد.
import sys
for p in sys.path:
print(p)
print("math" in sys.modules)
if __name__ == "__main__"
بما أن الاستيراد ينفّذ الملف، نحتاج طريقة للتفريق بين «شُغّلت كسكريبت» و«استُوردت». لذلك توجد المتغيّر __name__: عند تشغيل الملف يساوي "__main__"، وعند الاستيراد يساوي اسم الوحدة.
# categories.py
def guess(title: str) -> str:
return "طعام" if "مقهى" in title.lower() else "متنوع"
if __name__ == "__main__":
print(guess("مقهى قرب البيت")) # يُنفَّذ فقط عند python categories.py
وللحزمة بديل: ملف __main__.py داخلها، والتشغيل بالأمر python -m expenses.
الوحدات القياسية في Python
يأتي Python «مع البطاريات»: مئات الوحدات المدمجة متاحة فوراً دون تثبيت. أكثرها استخداماً:
| الوحدة | لماذا تُستخدم |
|---|---|
os، sys، pathlib، shutil | النظام والمسارات والملفات |
json، csv، pickle | صيغ البيانات |
datetime، time، calendar | التاريخ والوقت |
math، random، statistics، fractions | الأرقام |
re، string | معالجة النصوص |
collections، itertools، functools | بنى البيانات والمكررات |
logging، argparse، subprocess | السجلات ووسائط سطر الأوامر وتشغيل البرامج |
asyncio، threading | البرمجة غير المتزامنة والخيوط |
القائمة الكاملة لوحدات المكتبة القياسية موجودة في sys.stdlib_module_names (منذ Python 3.10). ولمعرفة ما تحتويه وحدة استخدم dir(math) وhelp(math).
حزمة خاصة بك: بنية المشروع
عندما تكثر الوحدات تُجمع في حزمة. والترتيب الشائع هو تخطيط src:
expenses-project/
├── pyproject.toml
└── src/
└── expenses/
├── __init__.py
├── cli.py
├── parsing.py
├── categories.py
└── report.py
تستورد الوحدات داخل الحزمة بعضها بعضاً إما باسم الحزمة الكامل (from expenses.categories import guess) أو نسبياً (from .categories import guess). والاستيراد النسبي يعمل داخل الحزمة فقط، لا في ملف يُشغَّل مباشرة كسكريبت.
pip في Python: تثبيت المكتبات
المكتبات الخارجية موجودة في الفهرس PyPI، وتُثبَّت بالأداة pip التي تأتي مع Python. والأضمن استدعاؤها عبر المفسّر نفسه، فتصل المكتبة إلى Python الذي تشغّل به الكود.
python -m pip install requests # تثبيت
python -m pip install "pandas>=2.3" # مع تحديد الإصدار
python -m pip install --upgrade requests
python -m pip uninstall requests # حذف
python -m pip list # قائمة الحزم المثبتة
python -m pip show requests # الإصدار والاعتماديات
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
على Windows يُستخدم غالباً المشغّل py بدلاً من python. وإذا لم يوجد pip فثبّته بالوحدة المدمجة: python -m ensurepip --upgrade.
مكتبات Python الشائعة
عدد الحزم في PyPI مئات الآلاف، لكن أغلب المشاريع تستخدم قائمة قصيرة واحدة. ابدأ بها بدل البحث عن الغريب.
| المهمة | المكتبات |
|---|---|
| طلبات HTTP والـ API | requests، httpx |
| تحليل البيانات والجداول | pandas، numpy |
| الرسوم البيانية | matplotlib |
| خدمات الويب | fastapi، django، flask |
| الاختبارات | pytest |
| الصور | pillow (يُستورد باسم PIL) |
انتبه إلى أن اسم التثبيت واسم الاستيراد لا يتطابقان دائماً. تُثبَّت pillow بالأمر pip install pillow ويُكتب from PIL import Image، وتُثبَّت opencv-python ويُستورد الاسم cv2. وإذا فشل import مباشرة بعد التثبيت فراجع في وثائق الحزمة الاسم الذي تُستورد به.
تثبيت المكتبات على Linux
في Debian وUbuntu الحديثتين ينتهي الأمر pip install في Python النظام بالخطأ externally-managed-environment (PEP 668). وهذه حماية: Python النظام يتبع مدير حزم النظام، ولا ينبغي لـ pip أن يكسره. والحل الصحيح ليس --break-system-packages، بل بيئة افتراضية.
venv في Python: البيئة الافتراضية
البيئة الافتراضية مجلد مستقل فيه نسخة من المفسّر (رابط إليه) ومجلد site-packages خاص به. المكتبات المثبتة فيها لا يراها إلا هذا المشروع. فيمكن لمشروع أن يستخدم Django 4، ولمشروع مجاور Django 5، دون تعارض.
وحدة venv مدمجة في Python ولا تحتاج إلى تثبيت منفصل (وفي Debian/Ubuntu قد تحتاج إلى الحزمة النظامية python3-venv).
# إنشاء بيئة في المجلد .venv
python -m venv .venv
# التفعيل: Linux وmacOS
source .venv/bin/activate
# التفعيل: Windows PowerShell
.venv\Scripts\Activate.ps1
# الآن يثبّت pip الحزم داخل .venv
python -m pip install requests
python -c "import sys; print(sys.prefix)"
# الخروج من البيئة
deactivate
بعد التفعيل يظهر (.venv) في بداية سطر الطرفية. لا يُرفع مجلد البيئة إلى git (أضف .venv/ إلى .gitignore) ولا يُنقل بين الأجهزة، بل تُحفظ قائمة الاعتماديات وتُعاد إنشاء البيئة منها. وإذا منع PowerShell تشغيل سكريبت التفعيل فاسمح به للمستخدم: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.
uv وpyproject.toml
المشاريع الحديثة تصف الاعتماديات في pyproject.toml. والمدير uv المستخدم في الدورة يجمع إنشاء البيئة والتثبيت معاً: uv add pandas تنزّل الحزمة، وتسجلها في pyproject.toml، وتثبّت الإصدارات الدقيقة في uv.lock. ويكفي لزميلك أن ينسخ المشروع وينفّذ uv sync ليحصل على البيئة نفسها تماماً.
[project]
name = "expenses"
version = "0.1.0"
requires-python = ">=3.15"
dependencies = [
"pandas>=2.3",
]
[project.scripts]
expenses = "expenses.cli:main"
الاستيراد الكسول في Python 3.15 (PEP 810)
أهم جديد في هذا الموضوع في الإصدار 3.15 هو الكلمة المفتاحية lazy. المكتبات الثقيلة تُحمَّل ببطء: في قياس الدورة على الصورة python:3.15-slim استغرق import pandas العادي نحو 415 ميلي ثانية، بينما استغرق lazy import pandas نحو 13 ميلي ثانية. وهذا فرق كبير لأداة سطر أوامر تُشغَّل من أجل --help.
lazy import pandas as pd
lazy from expenses.report import build_report
def main() -> None:
...
df = pd.read_csv(path) # التحميل الفعلي لـ pandas يحدث هنا
بدلاً من الوحدة يُوضع في الاسم كائن وكيل (types.LazyImportType)، ويحدث الاستيراد الحقيقي عند أول استخدام للاسم. وإذا لم يُستخدم الاسم أبداً فلا يُصرف أي وقت عليه.
قيود الاستيراد الكسول
- مسموح فقط في المستوى الأعلى للوحدة: داخل دالة أو صنف أو
try/exceptيعطيSyntaxError. - الصيغة
lazy from x import *ممنوعة. - يظهر
ModuleNotFoundErrorعند أول استخدام، لا عند بدء التشغيل. - الآثار الجانبية للوحدة (تسجيل الإضافات وما شابه) تتأجل زمنياً.
يمكن جعل كل الاستيرادات كسولة دفعة واحدة بالعلم -X lazy_imports=all أو بمتغير البيئة PYTHON_LAZY_IMPORTS=all، وتُحدَّد الاستثناءات بـ sys.set_lazy_imports_filter(). وللكود الذي يجب أن يعمل على الإصدارات القديمة يوجد المتغير __lazy_modules__ = ["pandas"] داخل الوحدة: يجعل الإصدار 3.15 الاستيرادات المذكورة كسولة، أما الإصدارات القديمة فتنفّذ import العادي. ولمعرفة الوقت الذي يضيعه بدء التشغيل استخدم python -X importtime -c "import pandas". وتستفيد البرامج التي تُشغَّل كثيراً وبسرعة، أما الخادم طويل التشغيل فلا يكسب من ذلك شيئاً يُذكر.
أخطاء شائعة
ModuleNotFoundError: No module named 'requests'— المكتبة ثُبِّتت في Python آخر، أو لم تُفعَّل البيئة. تحقق بـpython -m pip show requestsبالمفسّر نفسه.- سمّيت ملفك
json.pyأوrandom.pyأوcsv.py— مجلد السكريبت يأتي أولاً فيsys.pathفيحجب الوحدة القياسية. يقترح Python 3.13 وما بعده في رسالة الخطأ «consider renaming». أعد تسمية الملف واحذف مجلد__pycache__. - استيراد دائري: الملف
a.pyيستوردb.pyوالعكس. انقل المشترك إلى وحدة ثالثة، أو استورد داخل الدالة. - تشغيل pip داخل المفسّر (
>>> pip install) يعطيSyntaxError. أوامر pip تُنفَّذ في الطرفية.
الخلاصة
الوحدة ملف، والحزمة مجلد وحدات، والمكتبة مجموعة جاهزة منها. يبحث import عن الوحدة في الذاكرة المؤقتة وفي sys.path وينفّذها مرة واحدة. ثبّت المكتبات الخارجية بـ pip أو uv داخل بيئة venv فقط، فلا تتداخل المشاريع ويسهل إعادة إنشاء البيئة. وقد شُرحت هذه الموضوعات عملياً في دورة بايثون، حيث يتحول متتبّع المصروفات إلى حزمة لها اعتماديات، وتتسارع بدايتها بالاستيراد الكسول.
الأسئلة الشائعة
هل يجب إنشاء venv لكل مشروع؟
نعم، فهذه ممارسة جيدة: لكل مشروع إصدارات مكتبات خاصة به، والبيئة المنفصلة تمنع التعارضات ولا تلوّث Python النظام.
ما الفرق بين pip وuv؟
pip يثبّت الحزم فقط، أما uv فيُنشئ البيئة ويسجّل الاعتماديات في pyproject.toml ويثبّت الإصدارات الدقيقة في ملف القفل، وهو أسرع بوضوح.
كيف أرى قائمة المكتبات المثبتة في Python؟
نفّذ python -m pip list في البيئة المطلوبة. أما pip freeze فيعرض القائمة نفسها بصيغة requirements.txt.
أين تُخزَّن الوحدات المثبتة؟
في مجلد site-packages الخاص بالبيئة الحالية. ويمكن معرفة مسار وحدة بالخاصية __file__، مثلاً: import requests; print(requests.__file__).
هل يمكن نقل مجلد venv إلى جهاز آخر؟
لا، فالبيئة مرتبطة بمسارات المفسّر في الجهاز الأصلي. ينقل الناس قائمة الاعتماديات وينشئون البيئة من جديد.
يُشرح هذا الموضوع بالتفصيل مع التطبيق العملي في دورة «Python 3.15: от новичка до профи».
الانتقال إلى الدورة