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 и поместите модуль внутрь. Это соответствие устраняет ошибку импорта и делает рабочее пространство предсказуемым.