Как исправить «Попытка относительного импорта в неупакованном виде» даже с __init__.py


744

Я пытаюсь следовать 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 » и « Импортировать модуль из относительного пути », но они не помогли.

Есть что-то, что я здесь скучаю?


17
Меня также очень смутили различные способы структурирования unittestпроектов, поэтому я написал этот довольно исчерпывающий пример проекта, который охватывает глубокое вложение модулей, относительный и абсолютный импорт (где работают и нет), а также относительные и абсолютные ссылки изнутри пакет, а также одиночный, двойной и импорт классов на уровне пакета. Помогите прояснить вещи прямо для меня!
cod3monk3y

1
Я не мог заставить ваши тесты работать. Продолжайте получать, no module named myimports.fooкогда я управляю ими.
Blairg23

@ Blairg23 Я предполагаю , что предполагаемый вызов является cdв PyImports, и запустить python -m unittest tests.test_abs, например.
duozmo

7
Я согласен с Джином. Хотелось бы, чтобы был механизм для отладки процесса импорта, который был бы немного более полезным. В моем случае у меня есть два файла в одном каталоге. Я пытаюсь импортировать один файл в другой файл. Если у меня есть файл init .py в этом каталоге, я получаю относительный импорт ValueError: Попытка относительного импорта в непакетной ошибке. Если я удаляю файл init .py, то получаю ошибку: нет модуля с именем 'NAME' error.
user1928764

В моем случае у меня есть два файла в одном каталоге. Я пытаюсь импортировать один файл в другой файл. Если у меня есть файл init .py в этом каталоге, я получаю относительный импорт ValueError: Попытка относительного импорта в непакетной ошибке. Если я удаляю файл init .py, то получаю ошибку: нет модуля с именем 'NAME' error. Что действительно расстраивает, так это то, что у меня это сработало, а потом я выстрелил себе в ногу, удалив файл .bashrc, который установил PYTHONPATH на что-то, и теперь он не работает.
user1928764

Ответы:


443

Да. Вы не используете это как пакет.

python -m pkg.tests.core_test

51
Готча: обратите внимание, что в конце нет .py!
Разумник

497
Я не являюсь ни одним из downvoters, но я чувствую, что это могло бы использовать немного больше деталей, учитывая популярность этого вопроса и ответа. Отмечая такие вещи, как из какого каталога выполнять вышеупомянутую команду оболочки, тот факт, что вам нужно пройти __init__.pyвесь путь до __package__конца , и хитрость -modifying (описанную ниже BrenBarn), необходимые для того, чтобы разрешить этот импорт для исполняемых скриптов (например, при использовании shebang и делать ./my_script.pyв оболочке Unix) было бы все полезно. Весь этот вопрос был довольно сложным для меня, чтобы выяснить или найти краткую и понятную документацию.
Марк Амери

16
Примечание: вы должны быть вне каталога pkgв той точке, где вы вызываете эту строку из CLI. Тогда все должно работать как положено. Если вы внутри pkgи звоните python -m tests.core_test, это не сработает. По крайней мере, это не для меня.
Blairg23

94
Серьезно, вы можете объяснить, что происходит в вашем ответе?
Буратино

18
@MarkAmery Почти сошел с ума, пытаясь понять, как все это работает, относительный импорт в проекте с подкаталогами с py-файлами, в которых есть __init__.pyфайлы, но вы продолжаете получать сообщение об ValueError: Attempted relative import in non-packageошибке. Я бы где-нибудь заплатил действительно хорошие деньги, чтобы наконец объяснить простым языком, как все это работает.
АдъюнктПрофессорФалькон

635

Чтобы развить ответ Игнасио Васкеса-Абрамса :

Механизм импорта Python работает относительно __name__текущего файла. Когда вы выполняете файл напрямую, он не имеет своего обычного имени, но "__main__"вместо этого имеет имя. Так что относительный импорт не работает.

Как предложил Игансио, вы можете выполнить его, используя -mопцию. Если у вас есть часть вашего пакета, предназначенная для запуска в качестве сценария, вы также можете использовать__package__ атрибут, чтобы сообщить этому файлу, какое имя он должен иметь в иерархии пакетов.

Смотрите http://www.python.org/dev/peps/pep-0366/ для подробностей.


55
Мне потребовалось некоторое время, чтобы понять, что вы не можете запустить python -m core_testиз testsподкаталога - это должно быть от родителя, или вы должны добавить родителя к пути.
Арам Кочарян

3
@DannyStaple: не совсем так. Вы можете использовать, __package__чтобы гарантировать, что исполняемые файлы сценариев могут относительно импортировать другие модули из того же пакета. Нет способа относительно импортировать из "всей системы". Я даже не уверен, почему ты хочешь сделать это.
BrenBarn

2
Я имею в виду, если для __package__символа установлено значение «parent.child», вы сможете импортировать «parent.other_child». Возможно, я не так хорошо это сформулировал.
Дэнни Стейпл

5
@DannyStaple: Хорошо, как это работает, описано в связанной документации. Если у вас есть сценарий script.pyв пакете pack.subpack, то установка его __package__на pack.subpackпозволит вам сделать , from ..module import somethingчтобы импортировать что - то из pack.module. Обратите внимание, что, как говорится в документации, вы все равно должны иметь пакет верхнего уровня в системном пути. Это уже так работает для импортированных модулей. Единственное, что можно сделать __package__, это позволить вам использовать это поведение и для непосредственно исполняемых скриптов.
BrenBarn

3
Я использую __package__в скрипте , который выполняется непосредственно , но , к сожалению, я получаю следующее сообщение об ошибке: «Родительский модуль„ххх“не загружен, не может выполнить относительный импорт»
Мононоке

202

Вы можете использовать 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__))))

35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))это также будет работать
Ajay

26
from os import sysвыглядит как измена :)
летающая овца

3
@Piotr: Это может считаться лучше, потому что он немного показывает более ясно, что добавляется sys.path- родительский каталог каталога, в котором находится текущий файл.
martineau

8
@flyingsheep: Согласен, я бы просто использовал обычный import sys, os.path as path.
Мартино

10
FYI, чтобы использовать это в IPython ноутбук, я приспособил этот ответ: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Тогда import components.coreу меня работает прямой , импортируя из родительского каталога записной книжки по желанию.
Гоночный головастик

195

Это зависит от того, как вы хотите запустить свой скрипт.

Если вы хотите запустить свой UnitTest из командной строки классическим способом, то есть:

python tests/core_test.py

Тогда, так как в этом случае «компоненты» и «тесты» являются братья и сестры папки, вы можете импортировать относительный модуль либо с помощью вставки или на добавление методы 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

3
что мне делать, если я пытаюсь использовать pdb для отладки? так как вы используете python -m pdb myscript.pyдля запуска сеанса отладки.
Дэнни

1
@dannynjust - это хороший вопрос, поскольку у вас не может быть 2 основных модулей. Обычно при отладке я предпочитаю заглядывать в отладчик вручную в первый момент, когда я хочу начать отладку. Вы можете сделать это, вставив import pdb; pdb.set_trace()в код (встроенный).
Мгилсон

3
Это лучше использовать insertвместо append? То есть,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine

2
Использование insert лучше подходит для относительной семантики импорта, где имена локальных пакетов имеют приоритет над установленными пакетами. Особенно для тестов, вы обычно хотите протестировать локальную версию, а не установленную (если ваша тестовая инфраструктура не устанавливает тестируемый код, в этом случае относительный импорт не требуется, и у вас не возникнет этой проблемы).
Алекс Дюпюи

1
Вы должны также упомянуть, что вы не можете находиться в каталоге, содержащем core_test, когда вы работаете как модуль (это было бы слишком просто)
Джозеф Гарвин


10

Если ваш вариант использования предназначен для выполнения тестов, и он показывает, что это так, то вы можете сделать следующее. Вместо того, чтобы запускать тестовый скрипт, python core_test.pyиспользуйте среду тестирования, такую ​​как pytest. Затем в командной строке вы можете ввести

$$ py.test

Это запустит тесты в вашем каталоге. Это обходит вопрос о __name__бытии , __main__который указывал на @BrenBarn. Затем поместите пустой __init__.pyфайл в ваш тестовый каталог, это сделает тестовый каталог частью вашего пакета. Тогда вы сможете сделать

from ..components.core import GameLoopEvents

Тем не менее, если вы запустите свой тестовый скрипт как основную программу, то снова произойдет сбой. Так что просто используйте тестовый бегун. Может быть, это также работает с другими участниками тестирования, например, nosetestsно я не проверял это. Надеюсь это поможет.


9

Мое быстрое исправление - добавить каталог в путь:

import sys
sys.path.insert(0, '../components/')

6
Ваш подход не будет работать во всех случаях, потому что часть «../» определяется из каталога, из которого вы запускаете свой скрипт (core_test.py). При вашем подходе вы вынуждены перейти к «тестам» перед запуском core_test.py scritp.
Xyman

7

Проблема связана с вашим методом тестирования,

ты пытался 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

Incase, если вы хотите импортировать как из hello.py

from .. import how

1
Лучше, чем принятый ответ
GabrielBB

В примере from .. import how, как вы импортируете определенный класс / метод из файла 'how'. когда я делаю эквивалент, from ..how import fooто я получаю «попытку относительного импорта за пределы пакета верхнего уровня»
Джеймс Халс

3

Старая нить. Я обнаружил, что добавление __all__= ['submodule', ...]в файл __init__.py и последующее использование from <CURRENT_MODULE> import *в целевом объекте работает нормально.


3

Вы можете использовать from pkg.components.core import GameLoopEvents, например, я использую pycharm, ниже приведен образ структуры моего проекта, я просто импортирую из корневого пакета, затем он работает:

введите описание изображения здесь


3
Это не сработало для меня. Вам нужно было указать путь в вашей конфигурации?
Мохаммад

3

Как сказал Паоло , у нас есть 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 проверено только.


3

Этот подход работал для меня и менее загроможден, чем некоторые решения:

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


1

Если кто-то ищет обходной путь, я наткнулся на один. Вот немного контекста. Я хотел проверить один из методов в файле. Когда я запускаю его изнутри

if __name__ == "__main__":

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

Вот что я сделал. Я только что создал лаунчер, внешнюю программу, которая будет импортировать необходимые методы и вызывать их. Хотя не очень хорошее решение, оно работает.


0

Вот один из способов, который разозлит всех, но работает довольно хорошо. В тестах запускаем:

ln -s ../components components

Затем просто импортируйте компоненты, как обычно.


0

Это очень запутанно, и если вы используете IDE, например, pycharm, это немного запутывает. Что сработало для меня: 1. Сделайте настройки проекта Pycharm (если вы запускаете python из VE или из каталога python) 2. Нет ничего плохого в том, как вы определили. иногда это работает с классом импорта из folder1.file1

если это не работает, используйте import folder1.file1. 3. Ваша переменная окружения должна быть правильно указана в системе или указана в аргументе командной строки.


-2

Поскольку ваш код содержит if __name__ == "__main__", который не импортируется как пакет, вам лучше использовать его sys.path.append()для решения проблемы.


Я не думаю, что наличие if __name__ == "__main__"в вашем файле имеет значение для всего, что связано с импортом.
user48956
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.