- Разрешение импорта внутри проекта в Python — Простое руководство
- Пример сломанного проекта
- ВАЖНЫЙ ПУТЬ
- Страх — это ПУТЬ на темную сторону
- Должен быть лучший способ
- Послесловие
- Почему не работает import в Python?
- Решение ошибки «ModuleNotFoundError: No module named ‘…’»
- Модуль не установлен
- Конфликт имен библиотеки и модуля
- Конфликт зависимостей модулей Python
Разрешение импорта внутри проекта в Python — Простое руководство
При импорте между файлами Python в проекте (т. е. импорте из одного файла проекта в другой) могут возникать исключения, такие как ModuleNotFoundError или ImportError , если один файл находится за пределами каталога другого. В следующей статье объясняется, почему это происходит и как устранить эти ошибки импорта, что включает в себя настройку редактора и/или установку проекта в виде пакета.
Пример сломанного проекта
Рассмотрим очень простой проект Python. Он имеет две папки кода, в каждой из которых находится файл кода Python; плюс тестовая папка с одним тестом.
project │ ├─── folder1 │ │ __init__.py │ │ file1.py │ ├─── folder2 │ │ __init__.py │ │ file2.py │ └─── tests │ test_file.py
project/folder1/file1.py содержит простую функцию add() .
# project/folder1/file1.py def add(num1, num2): return num1 + num2
project/folder2/file2.py — это файл Python, в который мы хотим импортировать и использовать нашу функцию add() . Мы используем абсолютный импорт, начиная с папки нашего проекта: from folder1 import file1 .
# project/folder2/file2.py from folder1 import file1 print('1 + 2 =', file1.add(1, 2))
Наконец, tests/test_file.py содержит простой метод тестирования в стиле pytest для нашей функции add() .
# project/tests/test_file.py from folder1 import file1 def test_add(): assert file1.add(1, 2) == 3
Это один из самых простых проектов на Python, поэтому все должно работать идеально, верно? Не так много; как мы увидим ниже.
Во-первых, давайте попробуем запустить наш скрипт file2.py :
C:\. \project> py folder2/file2.py Traceback (most recent call last): File "C:\. \project\folder2\file2.py", line 1, in from folder1 import file1 ModuleNotFoundError: No module named 'folder1'
О, Боже! Наш сценарий споткнулся на том, что должно было быть очень простым импортом, выдав сообщение об ошибке ModuleNotFoundError: No module named ‘folder1’, а Python жаловался, что не может найти рассматриваемый модуль.
Давайте проверим, как работает наш тестовый бегун:
# output trimmed for brevity C:\. \project> pytest ImportError while importing test module 'C:\. \project\tests\test_file.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: C:\Program Files\Python39\lib\importlib\__init__.py:127: in import_module from folder1 import file1 E ModuleNotFoundError: No module named 'folder1' 1 error in 0.07s
Точно такая же ошибка снова. Фигово. Но, может быть, мы сможем это исправить, верно? Вместо этого попробуем использовать относительный импорт в file2.py :
# project/folder2/file2.py from ..folder1 import file1 print('1 + 2 =', file1.add(1, 2))
При этом импорте мы используем .. для перехода на одну папку вверх от того места, где мы сейчас находимся (т.е. переходим от folder2 к project ); прежде чем вернуться в folder1 и в file1.py .
(venv) C:\. \project> py folder2/file2.py Traceback (most recent call last): File "C:\. \project\folder2\file2.py", line 1, in from ..folder1 import file1 ImportError: attempted relative import with no known parent package
К сожалению, теперь мы получаем совсем другую ошибку. Python выдает сообщение об ошибке ImportError: попытка относительного импорта без известного родительского пакета и отказывается выполнять относительный импорт.
Итак, почему возникают эти ошибки? И как их можно исправить?
ВАЖНЫЙ ПУТЬ
Чтобы понять, почему возникают эти ошибки, сначала мы должны понять, как Python работает с импортом.
Python разрешает операторы импорта, используя переменную списка с именем sys.path . Эта переменная содержит список каталогов, и Python по очереди ищет в каждом каталоге пакет Python для импорта.
>>> import sys >>> for path in sys.path: . print(repr(path)) '' 'C:\\Program Files\\Python39\\python39.zip' 'C:\\Program Files\\Python39\\DLLs' 'C:\\Program Files\\Python39\\lib' 'C:\\Program Files\\Python39' 'C:\\Program Files\\Python39\\lib\\site-packages'
Как видите, в списке перечислены только те каталоги, в которых установлен Python. Так как же работает импорт внутри проекта? Ключ находится в самой первой записи; эта пустая строка » . Эта пустая строка представляет собой каталог, содержащий запущенный файл Python или в котором была вызвана оболочка Python REPL. В нашем приведенном выше примере, если мы запустим project/folder1/file1.py , то project/folder1/ будет представлять собой пустую строку.
Правила того, как Python решает, что он может импортировать из этих каталогов, сложно описать точно; сокращенная версия выглядит следующим образом: вы можете импортировать из внутри любого каталога в этом списке (включая подкаталоги), но вы не можете импортировать из вне их.
Если мы снова посмотрим на примерную структуру проекта:
project │ ├─── folder1 │ │ __init__.py │ │ file1.py │ ├─── folder2 │ │ __init__.py │ │ file2.py │ └─── tests │ test_file.py
Мы попытались запустить project/folder1/file1.py , что означает, что project/folder1/ находится в пути Python. Однако project/folder2/file2.py , из которого мы хотим импортировать, находится за пределами этого каталога. Нам нужно подняться на одну директорию вверх до project — Python не позволит нам сделать это в данный момент.
На самом деле мы хотим, чтобы папка project находилась в папке sys.path . Если бы папка проекта находилась в sys.path , то мы могли бы импортировать из любой папки или файла внутри нее.
Страх — это ПУТЬ на темную сторону
Поскольку sys.path — это список, мы можем напрямую изменить его в соответствии с нашими потребностями, если захотим. Мы можем добавить к нему так же, как вы можете с любым другим списком. Добавить корень project так же просто, как добавить абсолютный путь папки к sys.path .
Если мы поместим эту строку в наш скрипт file2.py ,
import sys sys.path.append(r'C:\. \project') from folder1 import file1 print( '1 + 2 =', file1.add(1, 2), )
тогда импорт будет правильно разрешен, когда мы запустим файл.
C:\. \project> py folder2/file2.py 1 + 2 = 3
Однако, как вы можете видеть, это довольно уродливый метод правильной настройки импорта. Во-первых, мы должны выполнить добавление sys.path , прежде чем пытаться импортировать file1 ; мы не можем сгруппировать все наши импорты вместе, как в любом обычном файле. Возможно, что более важно, эта операция будет работать только для этого файла в его нынешнем виде. Если бы мы хотели импортировать file1.py в другие файлы в структуре нашего проекта, мы могли бы поместить эту строку в другое место или добавить к sys.path в нескольких местах. Тем не менее, это некрасивый способ решить проблему.
Должен быть лучший способ
Правильное решение проблемы, не прибегая к взлому sys.path , включает два основных подхода.
Если вы запускаете свой код только через конфигурации запуска PyCharm или Visual Studio Code, вы можете настроить эти редакторы для добавления корневой папки вашего проекта в sys.path . В случае с PyCharm это делается автоматически; в случае с Visual Studio Code это необходимо делать вручную для каждого нового проекта.
Однако если вы вызываете свой код вручную через терминал, будь то через PyCharm или Visual Studio Code или любой другой экземпляр терминала; затем вы можете добавить корневую папку своего проекта в sys.path , установив свой проект в виде пакета.
Ниже приведены пошаговые инструкции о том, как выполнять эти подходы. Если вы используете PyCharm или Visual Studio Code, выберите эти статьи; если вы используете какой-либо другой редактор или IDE, вместо этого выберите статью «Текстовый редактор и терминал».
Послесловие
Проблемы, связанные с импортом внутри проекта, чрезвычайно распространены, и с ними сталкиваются программисты Python всех уровней квалификации. С ними может быть очень неприятно иметь дело. Информация об ошибках практически не дает указаний о том, как решить проблему, а онлайн-ресурсы, такие как StackOverflow, часто сбивают с толку, предоставляя множество ответов, многие из которых неверны и/или неполны. Я надеюсь, что вышеизложенное поможет предоставить простое руководство по разрешению импорта внутри проекта.
Информация в этих ресурсах была собрана из нескольких источников; наиболее важным источником была статья pytest о Надлежащих практиках интеграции. В приведенных выше руководствах подробно описана процедура первого раздела этой статьи.
Почему не работает import в Python?
Есть основной файл, а есть модуль, который содержит одну функцию. Они лежат в одной папке. В основном файле есть вызов функции из модуля.
Работает, если from test_function import abc, но не работает, если import test_function (хочу, чтобы весь последующий код из модуля в дальнейшем импортировался автоматически в основной файл). Я что-то делаю неправильно?
Простой 4 комментария
import test_function test_function.abc()
from test_function import * abc()
А как вызываете?
Если я правильно помню — во втором случае надо вызывать через модуль — test_function.abc()
Насколько я знаю, это импортирует все функции? А если нужно импортировать весь код?
Если верить описаниям, это должно происходить при import *имя файла*.
Честно говоря, я не силен в питоне и на этот вопрос ответа дать не могу. Могу только загуглить 🙂
https://pythonworld.ru/osnovy/rabota-s-modulyami-s.
Тут, вроде как, подробно описано.
>>> Насколько я знаю, это импортирует все функции? А если нужно импортировать весь код?
Для модулей только функции и классы нужны. Или вы хотите вызвать и исполнение кода, что не в функциях?
Если так, то при импорте происходит исполнение кода модуля, т.е. что не в функциях сразу же исполняется. Обычно, это как раз, не требуется и соответственно этого пытаются избежать таким способом:
def main(): pass # код который исполняется при запуске, а не при импорте if __name__ == '__main__': main()
__name__ будет равен __main__ при прямом запуске этого модуля, и имени файла без расширения (.py) при импорте. Для кругозора прочитайте про пространство имен в Python-е. Спойлер: модули(файлы *.py) в Python такие же объекты как и экземпляры классов, и работаем с ними соответственно
Решение ошибки «ModuleNotFoundError: No module named ‘…’»
В Python может быть несколько причин возникновения ошибки ModuleNotFoundError: No module named . :
- Модуль Python не установлен.
- Есть конфликт в названиях пакета и модуля.
- Есть конфликт зависимости модулей Python.
Рассмотрим варианты их решения.
Модуль не установлен
В первую очередь нужно проверить, установлен ли модуль. Для использования модуля в программе его нужно установить. Например, если попробовать использовать numpy без установки с помощью pip install будет следующая ошибка:
Traceback (most recent call last): File "", line 1, in ModuleNotFoundError: No module named 'numpy'
Для установки нужного модуля используйте следующую команду:
pip install numpy # или pip3 install numpy
Или вот эту если используете Anaconda:
Учтите, что может быть несколько экземпляров Python (или виртуальных сред) в системе. Модуль нужно устанавливать в определенный экземпляр.
Конфликт имен библиотеки и модуля
Еще одна причина ошибки No module named — конфликт в названиях пакета и модуля. Предположим, есть следующая структура проекта Python:
demo-project └───utils __init__.py string_utils.py utils.py
Если использовать следующую инструкцию импорта файла utils.py, то Python вернет ошибку ModuleNotFoundError .
>>> import utils.string_utils
Traceback (most recent call last):
File "C:\demo-project\utils\utils.py", line 1, in
import utils.string_utils
ModuleNotFoundError: No module named 'utils.string_utils';
'utils' is not a packageВ сообщении об ошибке сказано, что «utils is not a package». utils — это имя пакета, но это также и имя модуля. Это приводит к конфликту, когда имя модуля перекрывает имя пакета/библиотеки. Для его разрешения нужно переименовать файл utils.py.
Конфликт зависимостей модулей Python
Иногда может существовать конфликт модулей Python, который и приводит к ошибке No module named.
Следующее сообщение явно указывает, что _numpy_compat.py в библиотеке scipy пытается импортировать модуль numpy.testing.nosetester .
Traceback (most recent call last): File "C:\demo-project\venv\ Lib\site-packages\ scipy\_lib\_numpy_compat.py", line 10, in from numpy.testing.nosetester import import_nose ModuleNotFoundError: No module named 'numpy.testing.nosetester'
Ошибка ModuleNotFoundError возникает из-за того, что модуль numpy.testing.nosetester удален из библиотеки в версии 1.18. Для решения этой проблемы нужно обновить numpy и scipy до последних версий.
pip install numpy --upgrade pip install scipy --upgrade