Як виправити "Спроба відносного імпорту в непакеті" навіть за допомогою __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, наприклад.
дуозмо

7
Я згоден з Джин. Я хотів би, щоб був механізм налагодження процесу імпорту, який був трохи кориснішим. У моєму випадку я маю два файли в одному каталозі. Я намагаюся імпортувати один файл в інший файл. Якщо у мене в цьому каталозі є файл init .py, я отримую ValueError: Спроба відносного імпорту в непакетній помилці. Якщо я видаляю файл init .py, я отримую помилку, жоден модуль з назвою "NAME" помилка.
користувач1928764

У моєму випадку я маю два файли в одному каталозі. Я намагаюся імпортувати один файл в інший файл. Якщо у мене в цьому каталозі є файл init .py, я отримую ValueError: Спроба відносного імпорту в непакетній помилці. Якщо я видаляю файл init .py, я отримую помилку, жоден модуль з назвою "NAME" помилка. Що насправді засмучує те, що я працював над цим, і тоді я вистрілив собі в ногу, видаливши файл .bashrc, який встановив PYTHONPATH на щось, і тепер він не працює.
користувач1928764

Відповіді:


443

Так. Ви не використовуєте його як пакет.

python -m pkg.tests.core_test

51
Отримка: Зауважте, що в кінці немає ".py"!
mindthief

497
Я не є жодною з низовин, але я вважаю, що це могло б використовувати трохи більше деталей, враховуючи популярність цього питання та відповіді. Зауважуючи такі речі, як з того каталогу, щоб виконати вищезазначену команду оболонки, той факт, що вам потрібен __init__.pys весь шлях вниз, і __package__хитрість-модифікація (описана нижче 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помилку. Я б заплатив по-справжньому хороші гроші за когось, щоб нарешті пояснити простою англійською мовою, як все це працює.
AdjunctProfessorFalcon

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". Можливо, я не так добре його фразував.
Danny Staple

5
@DannyStaple: Ну, як це працює, описано у доданій документації. Якщо у вас є сценарій script.pyв пакеті pack.subpack, то установка його __package__на pack.subpackдозволить вам зробити , from ..module import somethingщоб імпортувати що - то з pack.module. Зауважте, що, як йдеться в документації, ви все одно повинні мати пакет верхнього рівня на системному шляху. Це вже спосіб роботи імпортованих модулів. Єдине, __package__що потрібно - це дозволити вам використовувати таку поведінку і для безпосередньо виконуваних сценаріїв.
BrenBarn

3
Я використовую __package__в скрипті, який виконується безпосередньо, але, на жаль, я отримую таку помилку: "Батьківський модуль 'xxx' не завантажений, не може виконати відносний імпорт"
mononoke

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__), '..')))це також спрацює
відмовляйте

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працює для мене, імпортуючи з батьківського каталогу ноутбука за бажанням.
змагання з Tadpole

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для запуску сеансу налагодження.
danny

1
@dannynjust - Це гарне запитання, оскільки у вас немає двох основних модулів. Як правило, під час налагодження я вважаю за краще вручну впадати в налагоджувач в першій точці, де я хочу розпочати налагодження. Це можна зробити, вставивши import pdb; pdb.set_trace()в код (рядок).
mgilson

3
Краще використовувати insertзамість append? Тобто,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine

2
Використання вкладиша - це найкраща відповідність відносної семантики імпорту, де імена місцевих пакетів мають перевагу над встановленими пакетами. Спеціально для тестів, як правило, ви хочете протестувати локальну версію, а не встановлену (якщо тільки ваша тестова інфраструктура не встановить тестований код; у цьому випадку відносний імпорт не потрібний, і у вас не виникне цієї проблеми).
Алекс Дюпюй

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.
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

якщо ви хочете імпортувати як з hello.py

from .. import how

1
Краща, ніж прийнята відповідь
ГабріельББ

У прикладі from .. import 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. Неправильно, як ви визначили. колись це працює з каталогу import1.file1 import

якщо вона не працює, використовуйте 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.