2025, Nov 17 18:03
Как устранить ModuleNotFoundError в Python: плоский модуль или пакет
Разбираем, почему Python не видит myLib.main, и как исправить ModuleNotFoundError: выбрать плоский модуль или пакет, настроить структуру проекта и импорты.
Исправление ModuleNotFoundError для внутренних пакетов рабочего пространства чаще всего упирается в то, как Python ищет модули и как при этом организован проект. Если приложение пытается импортировать myLib.main, а библиотека устроена как «плоский» модуль, а не пакет, во время выполнения Python не находит myLib и падает с ModuleNotFoundError.
Как воспроизвести проблему
Рассмотрим такую структуру рабочего пространства, где приложение импортирует функцию из внутренней библиотеки:
myProject
├── pyproject.toml
├── apps
│ └── myApp
│ ├── pyproject.toml
│ └── main.py
└── lib
└── myLib
├── pyproject.toml
├── main.py
└── __init__.py
Библиотека содержит простую функцию:
# lib/myLib/main.py
def say_hi():
return "Hello!"
Приложение пытается импортировать её как подмодуль myLib:
# apps/myApp/main.py
from myLib.main import say_hi
print(say_hi())
При запуске приложения получаем:
ModuleNotFoundError: No module named 'myLib'
Что на самом деле происходит
Путь, который добавляется в поисковый путь импорта Python, соответствует корню проекта установленного дистрибутива, а не автоматически вложенному имени пакета. В текущей структуре проект myLib содержит верхнеуровневый файл main.py. Этот файл доступен для импорта как модуль верхнего уровня с именем main, а не как myLib.main. Иначе говоря, для Python существует модуль main, но нет пакета myLib, у которого есть подмодуль main, поэтому from myLib.main import ... в такой организации работать не может.
Два способа решить проблему
Решение зависит от того, какой стиль импорта вы хотите сохранить.
Если вас устраивает «плоская» схема, импортируйте модуль напрямую как main. В этом случае оставьте структуру библиотеки как есть и измените импорт в приложении на:
# apps/myApp/main.py
from main import say_hi
print(say_hi())
Если вам принципиально импортировать через myLib.main, превратите библиотеку в полноценный пакет: поместите исходники в каталог myLib внутри проекта. После реорганизации библиотека будет выглядеть так:
lib
└── myLib <- project root added to search path
├── pyproject.toml
└── myLib <- package directory
├── __init__.py
└── main.py <- import path is myLib.main
С такой структурой исходный код приложения будет работать без изменений:
# apps/myApp/main.py
from myLib.main import say_hi
print(say_hi())
Если выбираете между «плоским» модулем и размещением кода в каталоге пакета, стоит взглянуть на обсуждение о src‑layout и flat‑layout: https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/
Почему это важно
Понимание того, как пути импорта соотносятся с файловой структурой, помогает избежать ускользающих ModuleNotFoundError во время выполнения. Когда желаемый путь импорта согласован со структурой проекта, и инструментам, и разработчикам проще работать с кодовой базой.
Выводы
Согласуйте импорты со структурой. Если код находится в виде одного файла модуля в корне проекта — импортируйте его напрямую как main. Если нужен «именованный» импорт вроде myLib.main — создайте в проекте каталог пакета myLib и поместите модуль внутрь. Это соответствие устраняет ошибку импорта и делает рабочее пространство предсказуемым.