Как исправить "Попытка относительного импорта в не-пакет" даже с __init__.py
Я пытаюсь следовать PEP 328 со следующей структурой каталогов:
pkg/
__init__.py
components/
core.py
__init__.py
tests/
core_test.py
__init__.py
В core_test.py
У меня есть следующее заявление на импорт
from ..components.core import GameLoopEvents
Однако, когда я запускаю, я получаю следующую ошибку:
tests$ python core_test.py
Traceback (most recent call last):
File "core_test.py", line 3, in <module>
from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package
При поиске я обнаружил, что " относительный путь не работает даже с __init__.py" и " Импортировать модуль из относительного пути", но они не помогли.
Есть ли что-то, что я здесь скучаю?
22 ответа
Да. Вы не используете это как пакет.
python -m pkg.tests.core_test
Чтобы развить ответ Игнасио Васкеса-Абрамса:
Механизм импорта Python работает относительно __name__
текущего файла. Когда вы выполняете файл напрямую, он не имеет своего обычного имени, но имеет "__main__"
как его имя вместо. Так что относительный импорт не работает.
Вы можете, как предложил Игансио, выполнить его, используя -m
вариант. Если у вас есть часть вашего пакета, предназначенная для запуска в качестве сценария, вы также можете использовать __package__
атрибут, сообщающий этому файлу, какое имя он должен иметь в иерархии пакетов.
Смотрите http://www.python.org/dev/peps/pep-0366/ для подробностей.
Это зависит от того, как вы хотите запустить свой скрипт.
Если вы хотите запустить свой UnitTest из командной строки классическим способом, то есть:
python tests/core_test.py
Затем, поскольку в этом случае "компоненты" и "тесты" являются папками-братьями и сестрами, вы можете импортировать соответствующий модуль, используя метод insert или append модуля sys.path. Что-то вроде:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents
В противном случае вы можете запустить свой скрипт с аргументом '-m' (обратите внимание, что в данном случае речь идет о пакете, и поэтому вы не должны давать расширение '.py'), то есть:
python -m pkg.tests.core_test
В таком случае вы можете просто использовать относительный импорт, как вы это делали:
from ..components.core import GameLoopEvents
Наконец, вы можете смешать два подхода, чтобы ваш скрипт работал независимо от того, как он называется. Например:
if __name__ == '__main__':
if __package__ is None:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents
else:
from ..components.core import GameLoopEvents
Ты можешь использовать import components.core
непосредственно, если вы добавляете текущий каталог к sys.path
:
if __name__ == '__main__' and __package__ is None:
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
В core_test.py сделайте следующее:
import sys
sys.path.append('../components')
from core import GameLoopEvents
Проблема связана с вашим методом тестирования,
ты пытался python core_test.py
тогда вы получите эту ошибкуValueError: Попытка относительного импорта не в пакете
Причина: вы тестируете свою упаковку не из пакета.
так что протестируйте свой модуль из источника пакета.
если это структура вашего проекта,
pkg/
__init__.py
components/
core.py
__init__.py
tests/
core_test.py
__init__.py
cd pkg
python -m tests.core_test # dont use .py
или извне pkg/
python -m pkg.tests.core_test
Один .
если вы хотите импортировать из папки в том же каталоге. на каждый шаг назад добавляйте еще один.
hi/
hello.py
how.py
в how.py
from .hi import hello
если вы хотите импортировать, как из hello.py
from .. import how
Если ваш вариант использования предназначен для запуска тестов, и он показывает, что это так, то вы можете сделать следующее. Вместо запуска вашего тестового скрипта как python core_test.py
использовать среду тестирования, такую как pytest
, Затем в командной строке вы можете ввести
$$ py.test
Это запустит тесты в вашем каталоге. Это обходит проблему __name__
являющийся __main__
на что указал @BrenBarn. Далее ставим пустой __init__.py
файл в ваш тестовый каталог, это сделает тестовый каталог частью вашего пакета. Тогда вы сможете сделать
from ..components.core import GameLoopEvents
Тем не менее, если вы запустите свой тестовый скрипт как основную программу, то снова произойдет сбой. Так что просто используйте тестовый бегун. Может быть, это также работает с другими участниками тестирования, такими как nosetests
но я не проверял это. Надеюсь это поможет.
Мое быстрое решение заключается в добавлении каталога к пути:
import sys
sys.path.insert(0, '../components/')
Поскольку вы уже отметили все как модуль, нет необходимости использовать относительную ссылку, если вы запускаете как модуль python.
Вместо того
from ..components.core import GameLoopEvents
просто
from pkg.components.core import GameLoopEvents
При запуске от родительского элемента pkg используйте следующее
python -m pkg.tests.core_test
Как сказал Паоло, у нас есть 2 метода вызова:
1) python -m tests.core_test
2) python tests/core_test.py
Одно из различий между ними - строка sys.path[0]. Поскольку интерпретатор будет искать sys.path при выполнении импорта, мы можем сделать с tests/core_test.py
:
if __name__ == '__main__':
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from components import core
<other stuff>
И еще после этого мы можем запустить core_test.py с другими методами:
cd tests
python core_test.py
python -m core_test
...
Обратите внимание, что тестируется py36.
Этот подход сработал для меня и менее загроможден, чем некоторые решения:
try:
from ..components.core import GameLoopEvents
except ValueError:
from components.core import GameLoopEvents
Родительский каталог находится в моем PYTHONPATH, и есть __init__.py
файлы в родительском каталоге и в этом каталоге.
Вышеупомянутое всегда работало в python 2, но python 3 иногда попадал в ImportError или ModuleNotFoundError (последний является новым в python 3.6 и подклассом ImportError), поэтому следующая настройка работает для меня как в python 2, так и в 3:
try:
from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
from components.core import GameLoopEvents
Старая нить. Я узнал, что добавление __all__= ['submodule', ...]
в файл __init__.py, а затем с помощью from <CURRENT_MODULE> import *
в мишени работает отлично.
Ты можешь использовать from pkg.components.core import GameLoopEvents
Например, я использую pycharm, ниже приведено изображение структуры моего проекта, я просто импортирую его из корневого пакета, затем он работает:
не работает с относительным импортом
Проблема в том, что относительный импорт не работает при запуске модуля из командной строки.
python <main_module>.py
Это четко указано в PEP 338.
Выпуск 2.5b1 показал удивительное (хотя и очевидное в ретроспективе) взаимодействие между этим PEP и PEP 328 — явный относительный импорт не работает из основного модуля . Это связано с тем, что относительный импорт зависит от определения текущего положения модуля в иерархии пакетов. В основном модуле значение всегда равно
, поэтому явный относительный импорт всегда будет давать сбой (поскольку он работает только для модуля внутри пакета).
Причина
Проблема на самом деле не уникальна для переключателя -m. Проблема в том, что относительный импорт основан на , а в основном модуле
всегда имеет значение . Следовательно, относительный импорт в настоящее время не может работать должным образом из основного модуля приложения , потому что основной модуль не знает, где он действительно вписывается в пространство имен модулей Python (по крайней мере теоретически это можно исправить для основных модулей, выполняемых через -m, но напрямую исполняемым файлам и интерактивному интерпретатору совсем не повезло).
Чтобы лучше понять, см. Относительный импорт в Python 3 для подробного объяснения и того, как его преодолеть.
Если кто-то ищет обходной путь, я наткнулся на один. Вот немного контекста. Я хотел проверить один из методов в файле. Когда я запускаю его изнутри
if __name__ == "__main__":
он всегда жаловался на относительный импорт. Я попытался применить вышеупомянутые решения, но не сработал, так как было много вложенных файлов, каждый с несколькими импортами.
Вот что я сделал. Я только что создал лаунчер, внешнюю программу, которая будет импортировать необходимые методы и вызывать их. Хотя не очень хорошее решение, оно работает.
Если структура вашего проекта будет выглядеть так:
project
|
| --- module1
| |
| file1.py
|
|-----module2
| |
| file2.py
и вы собираетесь импортировать file1.py из file2.py, вы можете сделать это в file2.py:
import sys
sys.path.append('.')
import file2
Я до сих пор не знаю, почему и как, но это сработало для меня.
У меня были похожие проблемы, и как инженер-программист я думаю, что некоторые из предложенных здесь решений не идеальны. Если вам нужен относительный импорт, вам не следует использовать try/except, а затем иногда выполнять абсолютный импорт. Кроме того, для запуска программы вам не нужно менять sys.path.
Кроме того, программа должна работать всегда, независимо от вашего текущего рабочего каталога и независимо от того, как вы ее запускаете.
Таким образом, я создал новую экспериментальную библиотеку импорта: ultraimport. Она позволяет выполнять импорт на основе файловой системы, независимо от того, как вы запускаете свой код.
Из исходного вопроса вы бы изменили свой core_test.py на что-то вроде
import ultraimport
GameLoopEvents = ultraimport('__dir__/../components/core.py', 'GameLoopEvents')
print(GameLoopEvents)
и он всегда найдет его, независимо от того, как вы запускаете свои тесты.
$ python -m tests.core_test
<class 'core.GameLoopEvents'>
python ./tests/core_test.py
<class 'core.GameLoopEvents'>
Я также поместил этот пример в папку примеров в репозитории git.
Поскольку библиотека является экспериментальной, мне интересны отзывы. Это работает для меня, но еще не широко протестировано.
Вот один способ, который всех разозлит, но работает очень хорошо. При запуске тестов:
ln -s ../components components
Затем просто импортируйте компоненты, как обычно.
Для меня только это сработало: мне пришлось явно установить значение пакета в родительский каталог и добавить родительский каталог в sys.path
from os import path
import sys
if __package__ is None:
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
__package__= "myparent"
from .subdir import something # the . can now be resolved
Теперь я могу напрямую запустить свой скрипт с помощью
python myscript.py
.
Это очень сбивает с толку, и если вы используете IDE, например pycharm, это немного запутывает. Что сработало для меня: 1. Сделайте настройки проекта pycharm (если вы запускаете python из VE или из каталога python) 2. Нет неправильного способа, который вы определили. иногда он работает с классом импорта folder1.file1
если не работает, используйте import folder1.file1 3. Переменная среды должна быть правильно указана в системе или указать ее в аргументе командной строки.
Поскольку ваш код содержит if __name__ == "__main__"
, который не импортируется как пакет, лучше использовать sys.path.append()
решить проблему.