Относительный импорт - 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 шт):
Импорт зависит от содержимого списка 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'>
Если говорить совсем коротко, то так стартовать программу не получится. В папке 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
Но вообще я лично советовал бы первый вариант, так оно гранулярнее и нагляднее получается и меньше неочевидных проблем в дальнейшем.
В общем, файл, который запускается первым, без специальных костыльных хаков не может импортировать из папок уровнем выше.