Относительный импорт - ModuleNotFoundError: No module named 'models' в python 3

Хочу импортировать класс из файла в одной директории в файл в другой директории. Несмотря на то, что есть файлы init.py, интерпретатор ругается на этот импорт, хотя синтаксис верный.

Иерархия выглядит так:

project/
├── models/
│   ├──__init__.py
│   └── gpt.py
├── telegram/
│   ├──__init__.py
│   └── bot.py
├── .env
└── requirements.txt

Вот сам импорт в bot.py:

from models import GPT

Скрипт пробовал запускать из VS Code кнопкой и выполнял в терминале команду python -m telegram.bot.

Возвращаемая ошибка:

from models import GPT ModuleNotFoundError: No module named 'models'


Ответы (2 шт):

Автор решения: Vitalizzare ушел в монастырь

Импорт зависит от содержимого списка sys.path. В этом списке находятся пути, где интерпретатор ищет загружаемые модули. При этом загружается тот, который будет найден первым. Если ничего не найдено, возвращается ошибка ModuleNotFoundError.

В начале списка по умолчанию стоит директория исполняемого скрипта. Благодаря этому, любой модуль в этой же директории импортируется инструкцией import my_module. Выполняя команду python -m telegram.bot из project, путь к этому каталогу автоматически включается в начало sys.path. Соответственно проблемы с импортированием models быть не должно. Если выражаться точнее, то я не сумел воспроизвести указанную вами ошибку (см. пример ниже, как я моделирую ваш проект).

Если проблема все же возникает, то в порядке эксперимента будет полезно вывести содержимое sys.path, чтобы понимать, где именно происходит поиск:

# bot.py

try:
    import models
except ModuleNotFoundError:
    import sys
    print(f'{sys.path = }', file=sys.stderr)
    raise

...

В общем случае проблему можно решить либо изменив структуру каталогов (например, переместив models в каталог рядом с исполняемым файлом), либо изменив содержимое списка sys.path (например, добавив в начало нужный путь для поиска models).

Второй вариант осуществляется запуском интерпретатора с переменной окружения PYTHONPATH содержащую путь к каталогу, где находится models:

PYTHONPATH="/path/to/project" python /path/to/project/telegram/bot.py

Или же мы можем добавить нужный путь в самой программе, используя свойство модуля __file__, если оно определено:

# bot.py

assert __file__ in globals()
project_path = __file__.rsplit('/', maxsplit=2)[0]
import sys
sys.path.insert(0, project_path)

from models import GPT
...

P.S. Это скрипт для моделирования вашей ситуации (проверено в Linux с версиями Python 2.7, 3.8-3.13). В нем создается проект project с каталогами models и telegram, после чего из каталога проекта выполняется указанная вами команда python -m telegram.bot:

#!/bin/sh
# Recreate the project file structure,
# then step in the project folder
# and run the telegram.bot as a module 
#
# project/
# ├── models/
# │   ├──__init__.py
# │   └── gpt.py
# └── telegram/
#     ├──__init__.py
#     └── bot.py

mkdir project
mkdir project/models
mkdir project/telegram

echo '"""The module with the GPT class definition"""
class GPT:
    @classmethod
    def sayhello(cls):
        print("Hello from" + repr(cls))
' > project/models/gpt.py
echo '"""Init part of the models module"""
from models.gpt import GPT
__all__ = ["GPT"]
' > project/models/__init__.py
echo '"""Init part of the telegram module"""
print("Starting the telegram module...")
' > project/telegram/__init__.py
echo '"""The bot inside the telegram module"""
from models import GPT
GPT.sayhello()
' > project/telegram/bot.py

cd project
python -m telegram.bot

После выполнения скрипта ожидается следующий вывод на экран:

Starting the telegram module...
Hello from <class 'models.gpt.GPT'>
→ Ссылка
Автор решения: Aleksei Trankov

Если говорить совсем коротко, то так стартовать программу не получится. В папке project нужен стартовый файл (например, __main__.py) и уже из него стартует всё остальное. Стартовать приложение из папки на том же уровне, что и папки с импортами, без свистоплясок не выйдет — то есть можно перед импортом программно подобавлять директории в path (см., например, как инициализируется Django — с прописанными в конфиге apps и так далее), но, по-моему, вы этого не хотите.

Из bot.py после этого можно делать импорт так:

from ..models.gpt import GPT

Либо, если в __init__.py модуля models прописано

from .gpt импорт GPT

Тогда в bot.py можно писать

from ..models import GPT

Но вообще я лично советовал бы первый вариант, так оно гранулярнее и нагляднее получается и меньше неочевидных проблем в дальнейшем.

В общем, файл, который запускается первым, без специальных костыльных хаков не может импортировать из папок уровнем выше.

→ Ссылка