یک بار، در یک پروژه‌ی Flask که روی سرور deployment می‌شد، سرویس بالا نمی‌آمد و در لاگ یک پیام ساده دیده می‌شد: ModuleNotFoundError: No module named "flask_cors". کد در لوکال بی‌نقص اجرا می‌شد، در Docker هم کار می‌کرد، ولی روی سرور اصلی نه. بعد از یک ساعت بررسی، فهمیدم که پکیج در محیط virtualenv نصب نشده بود و اسکریپت با Python سراسری اجرا می‌شد. آن روز یاد گرفتم که ModuleNotFoundError در پایتون، در ظاهر یک مشکل نصب پکیج به‌نظر می‌رسد، ولی در باطن، پنجره‌ای است به سمت مدیریت محیط، ساختار پروژه، و انضباط وابستگی‌ها.

خطای ModuleNotFoundError در Python دقیقاً چیست؟

Python یک استثنای داخلی به‌نام ModuleNotFoundError دارد که وقتی مطرح می‌شود که کد شما درخواست import یک ماژول یا پکیج را می‌دهد و Python آن را در هیچ‌کدام از مسیرهای جستجوی خود پیدا نمی‌کند. این خطا از نسخه‌ی Python 3.6 به بعد معرفی شد و در نسخه‌های قبلی، به‌عنوان ImportError با پیام مشابه نمایش داده می‌شد. پیام خطا معمولاً چنین شکلی دارد:

Traceback (most recent call last):
  File "script.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named "requests"

در این مثال، کد شما درخواست import پکیج requests را داده ولی Python آن را در مسیرهای جستجوی خود پیدا نکرده است. این خطا از نوع Exception است، نه SyntaxError، بنابراین در زمان اجرا رخ می‌دهد. در پروژه‌های واقعی، این خطا تقریباً همیشه به معنای یکی از این سه چیز است: پکیج نصب نیست، محیط اجرای اشتباه است، یا مسیر جستجو به‌درستی تنظیم نشده.

نکته‌ی ظریف این است که ModuleNotFoundError در پیام خود، نام دقیق ماژول مورد نظر را ذکر می‌کند. این نام، جهت جستجو را تعیین می‌کند. اگر با مبانی پایتون آشنایی ندارید، ابتدا آموزش پایتون از صفر را بخوانید تا مدل ذهنی درستی از ماژول‌ها و پکیج‌ها شکل بگیرد.

ModuleNotFoundError یک شکایت از وجود است، نه از کیفیت. Python می‌گوید ماژولی که درخواست کردی، در مسیرهای جستجوی من وجود ندارد. این خطا، یک پنجره به سمت محیط اجرا و مدیریت وابستگی‌های پروژه است.

تفاوت ModuleNotFoundError و ImportError

یکی از پرتکرارترین سؤالات تازه‌کارها، تفاوت ModuleNotFoundError و ImportError است. درک درست این تفاوت، اولین گام در تشخیص سریع است:

ModuleNotFoundError: وقتی رخ می‌دهد که Python ماژول را در هیچ‌کدام از مسیرهای sys.path پیدا نمی‌کند. یعنی ماژول واقعاً در محیط وجود ندارد یا مسیر آن در دسترس نیست.

ImportError: وقتی رخ می‌دهد که ماژول پیدا شده ولی در فرآیند بارگذاری یا import نام خاصی، مشکلی رخ می‌دهد. مثال: نام مورد نظر در ماژول وجود ندارد، یا circular import رخ داده است.

از نظر فنی، ModuleNotFoundError یک زیرکلاس ImportError است. یعنی هر ModuleNotFoundError یک ImportError هم هست، ولی برعکسش نه. بنابراین، اگر کدی ImportError را catch می‌کند، ModuleNotFoundError هم توسط آن catch می‌شود.

این تفکیک، در تشخیص کمک‌کننده است: اگر ModuleNotFoundError می‌گیرید، مشکل در پیدا کردن ماژول است (نصب، مسیر، virtualenv). اگر ImportError می‌گیرید، مشکل در بارگذاری ماژول است (نام، circular import، وابستگی). جزئیات کامل ImportError در خطای ImportError در پایتون آمده است.

مکانیزم import و شش مسیر جستجو

برای درک درست ModuleNotFoundError، باید مکانیزم جستجوی ماژول در Python را بشناسیم. وقتی کد شما درخواست import یک ماژول می‌دهد، Python به ترتیب این مسیرها را بررسی می‌کند:

مسیر اول: Built-in modules. ماژول‌هایی که در خود مفسر Python تعبیه شده‌اند، مثل sys، math، time. این ماژول‌ها همیشه در دسترس هستند.

مسیر دوم: Frozen modules. ماژول‌هایی که در فایل اجرایی Python تعبیه شده‌اند. این مسیر در محیط‌های embedded یا اجرایی کاربرد دارد.

مسیر سوم: پوشه‌ی اسکریپت اصلی. اگر اسکریپت را به‌طور مستقیم اجرا کنید (مثلاً python script.py)، پوشه‌ی حاوی اسکریپت در sys.path قرار می‌گیرد.

مسیر چهارم: PYTHONPATH. متغیر محیطی که می‌توانید مسیرهای اضافی را در آن تعریف کنید.

مسیر پنجم: مسیرهای استاندارد نصب. مسیرهایی مثل /usr/lib/python3.11/ روی Linux که پکیج‌های استاندارد در آن‌ها قرار دارند.

مسیر ششم: site-packages. پوشه‌ای که پکیج‌های نصب‌شده با pip در آن قرار می‌گیرند. این پوشه، در virtualenv متفاوت است.

اگر ماژول در هیچ‌کدام از این شش مسیر پیدا نشود، ModuleNotFoundError مطرح می‌شود. بنابراین، تشخیص ریشه، نیاز به بررسی این شش مسیر دارد.

نُه علت رایج این خطا

در تجربه‌ی من روی صدها پروژه‌ی Python، ModuleNotFoundError از نُه علت مشخص می‌آید. شناخت این علت‌ها، تشخیص را در چند ثانیه ممکن می‌کند.

  1. پکیج واقعاً نصب نیست. شایع‌ترین علت.
  2. virtualenv فعال نیست. کد با Python سراسری اجرا می‌شود.
  3. virtualenv اشتباه فعال است. کد با یک virtualenv دیگر اجرا می‌شود.
  4. مسیر پروژه در sys.path نیست. اجرای اسکریپت از مسیر اشتباه.
  5. نام import با نام پکیج متفاوت است. مثل pillow که با PIL import می‌شود.
  6. تداخل نسخه‌ها و وابستگی‌های متناقض. نصب چند نسخه از یک پکیج.
  7. حساسیت به بزرگی و کوچکی. تفاوت بین Image و image.
  8. تداخل با نام ماژول‌های داخلی. پوشه‌ای با نام پکیج استاندارد.
  9. مشکل deployment و Docker. محیط اجرا با محیط توسعه متفاوت است.

هر علت، نشانه‌های مخصوص به خود و راه‌حل اختصاصی دارد. در بخش‌های بعدی، هر علت را جداگانه باز می‌کنم.

پکیج واقعاً نصب نیست

شایع‌ترین علت ModuleNotFoundError، نصب‌نبودن واقعی پکیج است. این حالت در پروژه‌های تازه یا در محیط‌های جدید شایع است. مثال:

import requests
# ModuleNotFoundError: No module named "requests"

راه‌حل: نصب پکیج با pip:

pip install requests

ولی قبل از نصب، سه بررسی انجام دهید:

بررسی اول: نسخه‌ی pip و Python. مطمئن شوید که pip مربوط به همان Python است که اسکریپت را اجرا می‌کند:

which python
which pip
python --version
pip --version

اگر which python و which pip به مسیرهای متفاوتی اشاره می‌کنند، احتمالاً Python و pip از دو محیط متفاوت هستند. راه‌حل: استفاده از python -m pip install requests که تضمین می‌کند pip از همان Python استفاده می‌کند.

بررسی دوم: فعال بودن virtualenv. اگر از virtualenv استفاده می‌کنید، مطمئن شوید که فعال است:

# Linux/Mac
source venv/bin/activate

# Windows
venv\Scripts\activate

پس از فعال‌سازی، prompt خط فرمان تغییر می‌کند و نام virtualenv را نشان می‌دهد.

بررسی سوم: نصب در محیط صحیح. پس از فعال‌سازی virtualenv، نصب را انجام دهید:

pip install requests

نکته‌ی ظریف: در بعضی پروژه‌ها، requirements.txt یا pyproject.toml وجود دارد که وابستگی‌ها را تعریف می‌کند. در این حالت، به‌جای نصب تک‌تک پکیج‌ها، از دستور زیر استفاده کنید:

pip install -r requirements.txt

این رویکرد، تضمین می‌کند که همه‌ی وابستگی‌های پروژه نصب شوند. مبانی مدیریت وابستگی‌ها در آموزش پایتون از صفر آمده است.

virtualenv و محیط اجرای اشتباه

دومین علت شایع، مربوط به virtualenv و محیط اجرای اشتباه است. در تجربه‌ی من، این مشکل در پروژه‌هایی که چند محیط دارند (development، staging، production) بسیار شایع است.

سه سناریوی رایج:

سناریو اول: virtualenv فعال نیست. کد شما با Python سراسری اجرا می‌شود، در حالی که پکیج‌ها در virtualenv نصب شده‌اند. راه‌حل: فعال کردن virtualenv قبل از اجرای اسکریپت.

سناریو دوم: virtualenv اشتباه فعال است. چند پروژه روی سیستم شما وجود دارد و شما virtualenv پروژه‌ی دیگر را فعال کرده‌اید. راه‌حل: بررسی prompt و مسیر Python.

سناریو سوم: virtualenv خراب شده است. در بعضی موارد، virtualenv به‌دلیل به‌روزرسانی Python یا تغییر مسیر، خراب می‌شود. راه‌حل: ساخت مجدد virtualenv.

deactivate
rm -rf venv
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

نکته‌ی ظریف: در پروژه‌های تیمی، هر توسعه‌دهنده باید virtualenv مستقل داشته باشد. هرگز virtualenv را در git commit نکنید. در فایل .gitignore، venv/ و .venv/ را اضافه کنید. مبانی Git در مباحث نسخه‌بندی آمده است.

مسائل sys.path و PYTHONPATH

سومین علت، مربوط به sys.path و PYTHONPATH است. وقتی شما یک اسکریپت را اجرا می‌کنید، Python مسیر پوشه‌ی اسکریپت را به sys.path اضافه می‌کند، ولی این مسیر همیشه همان مسیری نیست که شما انتظار دارید.

مثال شایع: در یک پروژه‌ی ساختاریافته، شما از پوشه‌ی scripts اسکریپت را اجرا می‌کنید:

project/
    scripts/
        my_script.py
    my_package/
        __init__.py
        module.py

وقتی my_script.py را با python scripts/my_script.py اجرا می‌کنید، پوشه‌ی scripts/ در sys.path قرار می‌گیرد، نه پوشه‌ی project/. بنابراین، import پکیج my_package شکست می‌خورد.

راه‌حل‌ها:

راه اول: تنظیم PYTHONPATH

export PYTHONPATH=/path/to/project:$PYTHONPATH
python scripts/my_script.py

راه دوم: اجرای ماژول به‌جای اسکریپت

python -m scripts.my_script

در این حالت، پوشه‌ی ریشه‌ی پروژه در sys.path قرار می‌گیرد.

راه سوم: استفاده از pyproject.toml

در پروژه‌های مدرن، با pyproject.toml و pip install -e .، پکیج به‌عنوان پکیج نصب‌شده در دسترس قرار می‌گیرد و مشکل مسیر حل می‌شود.

مبانی کار با ساختار پروژه در مباحث توسعه‌ی Python آمده است. اگر با ماژول‌های Python آشنایی ندارید، JSON چیست و چگونه داده‌ها را ساختاردهی می‌کند مدل ذهنی خوبی از ساختاردهی ارائه می‌دهد.

نام پکیج و نام import

چهارمین علت، تفاوت بین نام پکیج و نام import است. در بعضی موارد، نامی که در PyPI ثبت شده با نامی که در کد import می‌کنید متفاوت است. مثال‌های رایج:

نام نصب (pip) نام import
pillow PIL
scikit-learn sklearn
beautifulsoup4 bs4
python-dateutil dateutil
opencv-python cv2
pyyaml yaml

اگر شما pip install pillow کنید ولی import pillow بنویسید، خطا می‌گیرید. راه‌حل: مستندات پکیج را بررسی کنید یا با pip show اطلاعات نصب را ببینید.

نکته‌ی ظریف: در بعضی موارد، پکیج در PyPI با نامی ثبت شده ولی در کد با نام دیگری import می‌شود چون نام اصلی در conflict با پکیج دیگری است. مثلاً pillow جایگزین PIL قدیمی است ولی نام import را حفظ کرده. مبانی پکیج‌ها در مباحث Python آمده است.

تداخل نسخه‌ها و وابستگی‌های متناقض

پنجمین علت، تداخل نسخه‌ها و وابستگی‌های متناقض است. این مشکل در پروژه‌های بزرگ که چند پکیج به هم وابسته‌اند، شایع است.

مثال: پروژه‌ای که هم package_a نسخه‌ی 1.0 و هم package_b نسخه‌ی 2.0 نیاز دارد، در حالی که این دو، وابستگی‌های متناقض دارند. در این حالت، pip ممکن است یک نسخه را حذف و نسخه‌ی دیگر را نصب کند و باعث ModuleNotFoundError شود.

راه‌حل: استفاده از ابزارهای مدیریت وابستگی مدرن:

  • pip-tools: برای قفل کردن نسخه‌ها با pip-compile.
  • poetry: مدیریت وابستگی با pyproject.toml و poetry.lock.
  • pdm: جایگزین مدرن poetry.
  • uv: ابزار بسیار سریع مدیریت پکیج از Astral.

در پروژه‌های واقعی، استفاده از یکی از این ابزارها، بخش بزرگی از تداخل نسخه‌ها را پیشگیری می‌کند. به‌ویژه، فایل lock که توسط این ابزارها تولید می‌شود، تمام نسخه‌ها را دقیق ثبت می‌کند و از نصب ناخواسته‌ی نسخه‌های ناسازگار جلوگیری می‌کند.

نکته‌ی ظریف: اگر پروژه‌ای روی چند محیط اجرا می‌شود (development، CI، production)، استفاده از فایل lock ضروری است. بدون این فایل، ممکن است در محیط production، نسخه‌ی جدیدتری از یک پکیج نصب شود که با کد شما سازگار نیست. مبانی کار با پکیج‌ها در آموزش پایتون از صفر آمده است.

حساسیت به بزرگی و کوچکی و نام‌های محلی

ششمین علت، حساسیت به بزرگی و کوچکی حروف است. Python به بزرگی و کوچکی حساس است، بنابراین import image و import Image دو import متفاوت هستند. این مشکل در سیستم‌های Windows و Mac (که فایل‌سیستم case-insensitive دارند) پنهان می‌ماند و روی Linux (که case-sensitive است) ظاهر می‌شود.

مثال کلاسیک: در Mac، پکیج Image به‌عنوان image import می‌شود چون فایل‌سیستم حساس نیست. روی Linux، این import شکست می‌خورد.

راه‌حل: همیشه از نام دقیق استفاده کنید که در مستندات پکیج ذکر شده.

مشکل مرتبط: تداخل با نام ماژول‌های داخلی. اگر پروژه‌ی شما پوشه‌ای با نام json یا os داشته باشد، Python آن را به‌جای ماژول استاندارد import می‌کند:

project/
    json/           # پوشه‌ی پروژه
        __init__.py
    script.py

# در script.py
import json  # Python این را به‌عنوان پوشه‌ی پروژه import می‌کند، نه ماژول استاندارد

راه‌حل: از نام‌هایی که با ماژول‌های استاندارد تداخل دارند، خودداری کنید. نام‌های امن: app_json، my_json، json_utils. مبانی نام‌گذاری در مباحث Python آمده است.

ModuleNotFoundError در Docker و deployment

هفتمین علت، مربوط به Docker و deployment است. در محیط‌های ایزوله مثل Docker، مسیرها و محیط‌ها متفاوت از لوکال هستند.

مشکلات رایج:

مشکل اول: نصب نکردن پکیج‌ها در Dockerfile. Dockerfile باید پکیج‌ها را در image نصب کند:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

مشکل دوم: تنظیم نکردن PYTHONPATH. در Docker، مسیر پروژه باید در PYTHONPATH باشد:

ENV PYTHONPATH=/app

مشکل سوم: تفاوت بین build و runtime. اگر پکیج در مرحله‌ی build نصب شده ولی در runtime در دسترس نیست، با ModuleNotFoundError مواجه می‌شوید. راه‌حل: استفاده از multi-stage build یا اطمینان از نصب پکیج‌ها در image نهایی.

مشکل چهارم: محیط virtualenv در Docker. در Docker معمولاً به virtualenv نیازی نیست چون خود container یک محیط ایزوله است. اگر از virtualenv استفاده می‌کنید، باید مسیر آن را در Dockerfile فعال کنید.

نکته‌ی ظریف: در Kubernetes یا محیط‌های orchestration، ممکن است چند container داشته باشید که هر کدام image متفاوتی دارد. راه‌حل: تعریف دقیق وابستگی‌ها در Helm chart یا yaml و استفاده از image مشخص.

در Django، Flask و FastAPI

در فریم‌ورک‌های وب Python، ModuleNotFoundError از منابع خاص خود می‌آید:

در Django

در settings.py، اگر در INSTALLED_APPS یک app را نام ببرید که وجود ندارد، Django هنگام startup خطا می‌دهد:

INSTALLED_APPS = [
    "myapp",       # ModuleNotFoundError اگر myapp نصب نباشد
    "rest_framework",
]

راه‌حل: بررسی نام appها و اطمینان از نصب پکیج‌های خارجی. مبانی کامل در آموزش جنگو برای مبتدیان و مباحث امنیتی در بهترین روش‌های امنیت Django آمده است.

در Flask

در Flask، وقتی یک blueprint یا extension را import می‌کنید، اگر پکیج نصب نباشد، خطا می‌گیرید:

from flask_sqlalchemy import SQLAlchemy
# ModuleNotFoundError اگر flask-sqlalchemy نصب نباشد

راه‌حل: نصب flask-sqlalchemy با pip و استفاده از requirements.txt. مبانی کامل در آموزش فلسک در پایتون آمده است.

در FastAPI

در FastAPI، خطای شایع مربوط به pydantic است:

from pydantic import BaseModel
# ModuleNotFoundError اگر pydantic نصب نباشد

راه‌حل: نصب کامل با pip install fastapi[all] که تمام وابستگی‌ها را نصب می‌کند.

در Django REST Framework

در DRF، خطای شایع مربوط به نبود rest_framework یا پکیج‌های مکمل مثل djangorestframework-simplejwt است. راه‌حل: نصب دقیق همه‌ی پکیج‌های مورد نیاز و اضافه کردن به requirements.txt.

در Jupyter و Google Colab

در محیط‌های تعاملی مثل Jupyter و Google Colab، ModuleNotFoundError از منابع خاص خود می‌آید:

مشکل اول: تفاوت kernel و محیط نصب. در Jupyter، kernel یک محیط جداگانه است. اگر پکیج را در محیط سراسری نصب کنید ولی kernel از virtualenv دیگری استفاده کند، پکیج پیدا نمی‌شود. راه‌حل: نصب پکیج در kernel فعلی:

!pip install package_name
# یا
import sys
!{sys.executable} -m pip install package_name

مشکل دوم: در Google Colab، پس از هر بار restart، پکیج‌ها حذف می‌شوند. راه‌حل: در ابتدای هر notebook، پکیج‌ها را دوباره نصب کنید یا از requirements.txt استفاده کنید.

مشکل سوم: در JupyterLab، پکیج‌های نصب‌شده در venv ممکن است در notebook قابل دسترسی نباشند. راه‌حل: نصب پکیج در kernel با !pip install یا استفاده از sys.executable.

مبانی کار با Jupyter در مباحث علم داده آمده است. اگر با Pandas کار می‌کنید، کتابخانه Pandas در پایتون نقطه‌ی شروع خوبی است.

روش تشخیص اصولی در چهار گام

در تجربه‌ی من، تشخیص ModuleNotFoundError در چند دقیقه انجام می‌شود، اگر روش سیستماتیک داشته باشید:

گام اول: خواندن دقیق پیام خطا. پیام ModuleNotFoundError دقیقاً می‌گوید کدام ماژول پیدا نشد. این نام، جهت جستجو را تعیین می‌کند.

گام دوم: بررسی نصب پکیج. با pip show بررسی کنید که پکیج نصب است یا نه:

pip show package_name

اگر پکیج نصب نیست، با pip install package_name نصب کنید.

گام سوم: بررسی محیط اجرا. با بررسی مسیر Python و pip، مطمئن شوید که هر دو از یک محیط هستند:

which python
which pip
python -c "import sys; print(sys.path)"

اگر virtualenv فعال نیست یا محیط اشتباهی است، آن را فعال یا اصلاح کنید.

گام چهارم: بررسی نام import. اگر پکیج نصب است ولی پیدا نمی‌شود، احتمالاً نام import با نام پکیج تفاوت دارد. با pip show و مستندات پکیج، نام import را بررسی کنید.

ابزارهای تشخیص:

  • pip show package_name: بررسی نصب پکیج.
  • pip list: لیست پکیج‌های نصب‌شده.
  • python -m pip list: لیست پکیج‌های Python فعلی.
  • pipdeptree: نمایش گراف وابستگی‌ها.
  • pip check: بررسی وابستگی‌های شکسته.

مبانی مدیریت خطا در مدیریت خطا در پایتون آمده است.

راهبردهای رفع اصولی

بعد از تشخیص، نوبت به رفع می‌رسد. راهبردهای رفع، بر اساس نوع خطا متفاوت است:

راهبرد اول: استفاده از python -m pip

به‌جای pip، از python -m pip استفاده کنید:

python -m pip install requests

این رویکرد تضمین می‌کند که pip مربوط به همان Python است که اسکریپت را اجرا می‌کند.

راهبرد دوم: استفاده از virtualenv

همیشه از virtualenv استفاده کنید:

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

این رویکرد، محیط ایزوله فراهم می‌کند و از تداخل نسخه‌ها جلوگیری می‌کند.

راهبرد سوم: استفاده از pyproject.toml

در پروژه‌های مدرن، pyproject.toml را جایگزین requirements.txt کنید:

[project]
name = "my_project"
version = "1.0.0"
dependencies = [
    "requests>=2.28.0",
    "pydantic>=2.0.0",
]

سپس با pip install -e . پروژه را نصب کنید.

راهبرد چهارم: استفاده از poetry یا uv

ابزارهای مدرن مثل poetry و uv، مدیریت وابستگی‌ها را ساده‌تر می‌کنند:

# poetry
poetry add requests

# uv
uv pip install requests

این ابزارها، فایل lock تولید می‌کنند که تمام نسخه‌ها را دقیق ثبت می‌کند.

راهبرد پنجم: imports تأخیری

برای ماژول‌های سنگین یا اختیاری، از imports تأخیری استفاده کنید:

def process_data():
    try:
        import pandas as pd
    except ImportError:
        raise RuntimeError("pandas not installed")
    return pd.DataFrame()

این رویکرد، startup برنامه را سریع‌تر می‌کند و خطاهای واضح‌تری در صورت نبود پکیج می‌دهد.

pyproject.toml و مدیریت مدرن وابستگی‌ها

در سال‌های اخیر، pyproject.toml به استاندارد مدیریت پروژه‌های Python تبدیل شده است. این فایل، جایگزین setup.py، requirements.txt و ابزارهای دیگر می‌شود.

ساختار پایه‌ی pyproject.toml:

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "my_project"
version = "1.0.0"
dependencies = [
    "requests>=2.28.0",
    "pydantic>=2.0.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
]

برای نصب پروژه در حالت editable:

pip install -e .
pip install -e ".[dev]"  # با وابستگی‌های توسعه

مزیت اصلی: پکیج پروژه در site-packages ثبت می‌شود و می‌توانید از هر جای محیط، آن را import کنید. این رویکرد، مشکل مسیر و sys.path را کاملاً حل می‌کند.

نکته‌ی ظریف: برای پروژه‌های بزرگ، ترکیب pyproject.toml با ابزارهای مثل poetry یا uv، بهترین رویکرد است. این ابزارها، فایل lock تولید می‌کنند که تمام وابستگی‌های غیرمستقیم را هم ثبت می‌کند و از نصب نسخه‌های ناسازگار جلوگیری می‌کند. مبانی کامل در مباحث توسعه‌ی Python آمده است.

در محیط production

در محیط production، ModuleNotFoundError ابعاد جدی‌تری دارد:

قطع کامل سرویس

اگر خطا در زمان startup برنامه رخ دهد، کل سرویس بالا نمی‌آید. این حالت، معمولاً بعد از deployment یا بعد از نصب یک وابستگی جدید رخ می‌دهد.

نشت اطلاعات

پیام خطا، مسیر فایل‌ها و نام پکیج‌ها را افشا می‌کند. راه‌حل: در production، لاگ‌ها را در جای امن نگه دارید و پیام عمومی به کاربر نشان دهید.

پایش و آلارم‌دهی

در production، ModuleNotFoundError باید به‌طور مناسب پایش شود. ابزارهایی مثل Sentry و Rollbar، این خطاها را جمع‌بندی می‌کنند. نکته: خطاهای import معمولاً در زمان startup رخ می‌دهند، بنابراین پایش باید از همان ابتدا فعال باشد.

پیشگیری با تست

بیشتر این خطاها را می‌توان قبل از production با تست‌های خودکار کشف کرد:

  • Import tests: تست import همه‌ی ماژول‌ها در CI.
  • Dependency check: تست نصب همه‌ی وابستگی‌ها از requirements.txt یا pyproject.toml.
  • Smoke tests: تست startup برنامه در محیط شبیه‌سازی‌شده.
  • CI matrix: تست روی چند نسخه‌ی Python و سیستم‌عامل.

مبانی تست در مباحث Python آمده است.

اشتباهات رایج در برخورد با این خطا

در طول سال‌ها، الگوهای تکراری از اشتباهات دیده‌ام که هر کدام می‌تواند پروژه را به چالش بکشد:

اشتباه اول: نصب با pip سراسری

نصب پکیج در محیط سراسری، منجر به تداخل نسخه‌ها می‌شود. راه‌حل: همیشه از virtualenv استفاده کنید.

اشتباه دوم: نادیده گرفتن requirements.txt

اگر requirements.txt را به‌روز نگه ندارید، در محیط‌های جدید، پکیج‌ها نصب نمی‌شوند. راه‌حل: به‌روزرسانی منظم با pip freeze > requirements.txt.

اشتباه سوم: استفاده از نسخه‌های بدون pin

در requirements.txt، نسخه‌ها را دقیق pin کنید:

requests==2.28.0
pydantic==2.0.0

این رویکرد، از نصب نسخه‌های ناسازگار جلوگیری می‌کند.

اشتباه چهارم: catch کردن عام Exception

استفاده از except Exception به‌جای except ModuleNotFoundError، خطاهای دیگر را هم پنهان می‌کند. راه‌حل: همیشه استثنای خاص را catch کنید.

اشتباه پنجم: نبود تست import در CI

اگر CI تست import را اجرا نکند، خطاهای import در production کشف می‌شوند. راه‌حل: در CI، یک اسکریپت ساده بنویسید که همه‌ی ماژول‌ها را import کند:

# test_imports.py
import my_project.module_a
import my_project.module_b
import my_project.module_c

اشتباه ششم: نادیده گرفتن تفاوت محیط‌ها

کد در لوکال کار می‌کند ولی در production خطا می‌دهد. ریشه: تفاوت نسخه‌ی پکیج‌ها، نسخه‌ی Python، و محیط. راه‌حل: استفاده از Docker یا محیط‌های ایزوله با تنظیمات مشابه.

اشتباه هفتم: فرض نصب بودن پکیج‌های پیش‌نیاز

در پروژه‌های جدید، اگر فرض کنید requests یا pandas نصب هستند، ممکن است در محیط‌های جدید با خطا مواجه شوید. راه‌حل: همیشه وابستگی‌ها را در فایل مدیریت پکیج ثبت کنید.

ModuleNotFoundError یک پیام از محیط است، نه از کد. کد شما درست است، ولی محیط اجرا با انتظارات شما سازگار نیست. راه‌حل، اصلاح محیط است نه سرکوب خطا.

پرسش‌های پرتکرار درباره خطای ModuleNotFoundError

این پرسش‌ها از دل تجربه‌ی عملی و جلسات مشاوره جمع‌آوری شده‌اند. پاسخ هر کدام بر اساس سناریوهای واقعی است.

تفاوت ModuleNotFoundError و ImportError چیست؟

ModuleNotFoundError زیرکلاس ImportError است که به‌طور خاص وقتی رخ می‌دهد که ماژول پیدا نشود. ImportError عمومی‌تر است و شامل خطاهای دیگر مثل نبود نام در ماژول یا خطاهای circular import می‌شود. جزئیات کامل در خطای ImportError در پایتون آمده است.

چرا این خطا در محیط production بیشتر دیده می‌شود؟

چون محیط production معمولاً از محیط development جداست و ممکن است پکیج‌ها در آن نصب نباشند، یا نسخه‌ها متفاوت باشند. راه‌حل: استفاده از Docker یا محیط‌های ایزوله با تنظیمات مشابه، و تست در staging قبل از production.

چگونه بفهمم که پکیج نصب است؟

با pip show package_name. اگر پکیج نصب باشد، اطلاعات آن نمایش داده می‌شود. اگر نصب نباشد، پیام «Package not found» نمایش داده می‌شود.

آیا virtualenv همیشه ضروری است؟

در پروژه‌های جدی، بله. virtualenv محیط ایزوله فراهم می‌کند و از تداخل نسخه‌ها جلوگیری می‌کند. در اسکریپت‌های کوچک، ممکن است ضروری نباشد، ولی توصیه می‌شود.

چرا pip install در Jupyter کار نمی‌کند؟

چون در Jupyter، kernel یک محیط جداگانه است. اگر پکیج را در محیط سراسری نصب کنید ولی kernel از virtualenv دیگری استفاده کند، پکیج پیدا نمی‌شود. راه‌حل: نصب در kernel فعلی با !pip install یا !{sys.executable} -m pip install.

آیا می‌توانم از try/except برای ModuleNotFoundError استفاده کنم؟

بله، در موارد خاص مثل imports اختیاری یا fallback پکیج‌ها، این رویکرد قابل توجیه است. مثال:

try:
    import ujson as json
except ModuleNotFoundError:
    import json

چرا نام import با نام پکیج متفاوت است؟

در بعضی موارد، نام پکیج در PyPI با نام import متفاوت است، چون نام اصلی در conflict با پکیج دیگری است. مثال: pillow (نام پکیج) با PIL (نام import). راه‌حل: مراجعه به مستندات پکیج.

چگونه در Docker، ModuleNotFoundError را حل کنم؟

سه رویکرد: اول، نصب کامل پکیج‌ها در Dockerfile با RUN pip install -r requirements.txt. دوم، تنظیم ENV PYTHONPATH=/app. سوم، استفاده از WORKDIR مناسب. مثال کامل در بخش Docker همین مقاله آمده است.

آیا poetry می‌تواند این خطا را پیشگیری کند؟

poetry با فایل lock، تمام وابستگی‌ها را دقیق ثبت می‌کند و از نصب نسخه‌های ناسازگار جلوگیری می‌کند. همچنین، poetry install همه‌ی وابستگی‌ها را از فایل lock نصب می‌کند. این رویکرد، بخش بزرگی از ModuleNotFoundErrorها را پیشگیری می‌کند.

چرا در Windows، pip با python هماهنگ نیست؟

در Windows، ممکن است چند نسخه‌ی Python نصب باشد و pip به یکی از آن‌ها اشاره کند، در حالی که python به نسخه‌ی دیگری. راه‌حل: استفاده از python -m pip install که تضمین می‌کند pip از همان Python استفاده می‌کند.

آیا ModuleNotFoundError روی performance تأثیر دارد؟

خود خطا در لحظه‌ی وقوع رخ می‌دهد. اگر مدیریت شود، از نظر performance هزینه‌ی ناچیزی دارد. ولی import ماژول‌های سنگین (مثل Pandas یا TensorFlow) می‌تواند زمان startup را افزایش دهد. راه‌حل: از imports تأخیری برای ماژول‌های سنگین استفاده کنید.

چگونه در pytest، ModuleNotFoundErrorها را تست کنم؟

با pytest.raises(ModuleNotFoundError):

import pytest

def test_missing_module():
    with pytest.raises(ModuleNotFoundError):
        import non_existent_module

چرا بعد از نصب پکیج، هنوز خطا می‌گیرم؟

سه دلیل: اول، پکیج در محیط اشتباهی نصب شده. دوم، virtualenv فعال نیست. سوم، نام import با نام پکیج متفاوت است. راه‌حل: بررسی مسیر Python و pip، فعال‌سازی virtualenv، و بررسی نام import.

آیا pip install --user این خطا را حل می‌کند؟

در بعضی موارد، اگر مشکل مربوط به دسترسی‌ها باشد، pip install --user کمک می‌کند. ولی این رویکرد، پکیج را در پوشه‌ی کاربر نصب می‌کند، نه در محیط فعال. توصیه می‌شود به‌جای آن از virtualenv استفاده کنید.

چگونه در CI/CD، این خطا را کشف کنم؟

سه رویکرد: اول، یک اسکریپت ساده بنویسید که همه‌ی ماژول‌ها را import کند. دوم، از pip check استفاده کنید. سوم، تست‌های integration که startup برنامه را بررسی می‌کنند.

آیا uv سرعت نصب را افزایش می‌دهد؟

بله، uv ابزار مدرن مدیریت پکیج از Astral است که چند برابر سریع‌تر از pip کار می‌کند. علاوه بر این، فایل lock تولید می‌کند و مدیریت وابستگی‌ها را ساده‌تر می‌کند. در پروژه‌های بزرگ، این سرعت اهمیت دارد.

تفاوت requirements.txt و pyproject.toml چیست؟

requirements.txt فقط لیست پکیج‌ها را ثبت می‌کند. pyproject.toml استاندارد مدرن است که اطلاعات کامل پروژه (نام، نسخه، وابستگی‌ها، تنظیمات build) را ثبت می‌کند و امکان نصب editable را فراهم می‌کند. برای پروژه‌های جدید، pyproject.toml توصیه می‌شود.

آیا ModuleNotFoundError می‌تواند ناشی از فایل‌های خراب باشد؟

غیرمستقیم، بله. اگر فایل پایتون ناقص یا خراب باشد، ممکن است در هنگام import خطاهای دیگری رخ دهد که به شکل ModuleNotFoundError ظاهر شوند. راه‌حل: بررسی فایل با python -m py_compile module.py.

چرا در Google Colab، پس از restart، پکیج‌ها حذف می‌شوند؟

چون Colab محیط موقت است و پس از restart، همه‌ی نصب‌های کاربر حذف می‌شوند. راه‌حل: در ابتدای هر notebook، پکیج‌ها را دوباره نصب کنید یا از requirements.txt استفاده کنید.

آیا ModuleNotFoundError در Kubernetes رفتار متفاوتی دارد؟

در Kubernetes، هر container image مستقل است. اگر image شامل پکیج‌های لازم نباشد، ModuleNotFoundError مطرح می‌شود. راه‌حل: تعریف دقیق وابستگی‌ها در Dockerfile و اطمینان از نصب کامل پکیج‌ها در image نهایی.

چرا pip install در virtualenv جدید، پکیج را در محیط قبلی نصب می‌کند؟

این مشکل معمولاً وقتی رخ می‌دهد که virtualenv جدید فعال نشده باشد. راه‌حل: بررسی prompt خط فرمان و اطمینان از فعال بودن virtualenv جدید. اگر مطمئن نیستید، از which pip و which python برای بررسی مسیرها استفاده کنید.

آنچه از سال‌ها کار با ModuleNotFoundError در Python آموختم

اگر بخواهم چکیده‌ی این سال‌ها را در چند جمله بگویم، سه اصل عملی دارم:

یک: virtualenv، سرمایه‌گذاری اولیه است. هر پروژه، حتی کوچک، باید virtualenv مستقل داشته باشد. این عادت، شما را از بخش بزرگی از ModuleNotFoundErrorها نجات می‌دهد. سرمایه‌گذاری چند دقیقه‌ای، در بلندمدت ساعت‌ها دیباگ را حذف می‌کند.

دو: pyproject.toml یا requirements.txt، سند رسمی پروژه است. همیشه وابستگی‌های پروژه را در یک فایل متمرکز ثبت کنید و آن را در git commit کنید. هر بار که پکیج جدیدی نصب می‌کنید، فایل را به‌روز کنید. این رویکرد، تضمین می‌کند که در محیط‌های جدید، همه‌ی پکیج‌ها نصب می‌شوند.

سه: تست import در CI، بیمه‌ی deployment است. یک اسکریپت ساده که همه‌ی ماژول‌ها را import کند، می‌تواند بخش بزرگی از ModuleNotFoundErrorها را قبل از deployment کشف کند. این رویکرد در پروژه‌های بزرگ، حیاتی است.

در کنار این سه اصل، یک هشدار عملی هم دارم: ModuleNotFoundError در نگاه اول یک مشکل ساده به‌نظر می‌رسد، ولی وقتی در چارچوب کلی مدیریت پروژه دیده شود، تبدیل به یک سیگنال می‌شود. این سیگنال می‌گوید که زیرساخت پروژه‌ی شما نیاز به انضباط بیشتری دارد. اگر این سیگنال را جدی بگیرید و ساختار را بهبود دهید، پروژه‌ی شما در ماه‌های بعد پایدارتر و قابل نگهداری‌تر خواهد بود.

هدف این مقاله، تمام‌کردن همه‌ی سناریوهای ممکن نبود. هدف، دادن یک چارچوب ذهنی برای تشخیص، پیشگیری و رفع این خطا بود. وقتی این چارچوب را درونی کنید، برخورد با ModuleNotFoundError از یک واکنش اضطراری به یک فرآیند منظم تبدیل می‌شود.

اگر ModuleNotFoundError در پروژه‌ی شما به شکلی ظاهر شده که با الگوهای این مقاله حل نشده، برای من جالب است بدانم کدام سناریو بود. تجربه‌ی خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر راه‌حلی پیدا کرده‌اید که هنوز در این مقاله نیست. 📦