после ошибки пакета верхнего уровня в относительном импорте

317

Кажется, здесь уже есть несколько вопросов об относительном импорте в Python 3, но, пройдя многие из них, я так и не нашел ответа на свой вопрос. так вот в чем вопрос.

У меня есть пакет, показанный ниже

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

и у меня есть одна строка в test.py:

from ..A import foo

Теперь я в папке package, и я бегу

python -m test_A.test

Я получил сообщение

"ValueError: attempted relative import beyond top-level package"

но если я нахожусь в родительской папке package, например, я запускаю:

cd ..
python -m package.test_A.test

все хорошо.

Теперь мой вопрос: когда я нахожусь в папке packageи запускаю модуль внутри подпакета test_A, так как test_A.test, исходя из моего понимания, ..Aподнимается только на один уровень, который все еще находится внутри packageпапки, почему он выдает сообщение, говорящее beyond top-level package. В чем именно причина этого сообщения об ошибке?

shelper
источник
49
в этом посте не объяснялась моя ошибка «за пределами пакета верхнего уровня»
shelper
4
У меня есть мысль, поэтому, когда при запуске test_A.test в качестве модуля, «..» идет выше test_A, который уже является высшим уровнем импорта test_A.test, я думаю, что уровень пакета не уровень каталога, а сколько Уровни вы импортируете пакет.
Шелпер
2
Обещаю, что вы поймете все об относительном импорте после просмотра этого ответа stackoverflow.com/a/14132912/8682868 .
pzjzeason
см. ValueError: попытка относительного импорта за пределы пакета верхнего уровня для подробного объяснения этой проблемы.
Напузба
Есть ли способ избежать относительного импорта? Например, как PyDev в Eclipse видит все пакеты в <PydevProject> / src?
Mushu909

Ответы:

173

РЕДАКТИРОВАТЬ: Есть лучшие / более последовательные ответы на этот вопрос в других вопросах:


Почему это не работает? Это потому, что python не записывает, откуда был загружен пакет. Таким образом, когда вы это делаете python -m test_A.test, он в основном просто отбрасывает знания, которые test_A.testна самом деле хранятся package(то packageесть не считается пакетом). Попытка from ..A import fooпытается получить доступ к информации, которой у него больше нет (например, каталоги соседних узлов загруженного местоположения). Концептуально это похоже на разрешение from ..os import pathв файле в math. Это было бы плохо, потому что вы хотите, чтобы пакеты были разными. Если им нужно использовать что-то из другого пакета, тогда они должны обращаться к ним глобально с помощью from os import pathи позволить Python выяснить, где это с $PATHи $PYTHONPATH.

Когда вы используете python -m package.test_A.test, тогда использование from ..A import fooрешает просто отлично, потому что он отслеживает, что находится, packageи вы просто получаете доступ к дочернему каталогу загруженного местоположения.

Почему Python не считает текущий рабочий каталог пакетом? НЕТ КЛИПА , но черт возьми, это было бы полезно.

Multihunter
источник
2
Я отредактировал свой ответ, чтобы обратиться к лучшему ответу на вопрос, который равняется тому же самому. Есть только обходные пути. Единственная вещь, которую я на самом деле видел в работе - это то, что сделал OP, то есть использует -mфлаг и запускается из каталога выше.
Multihunter
1
Следует отметить, что этот ответ по ссылке, предоставленной Multihunter, касается не sys.pathвзлома, а использования setuptools , что, на мой взгляд, гораздо интереснее.
Анджело Карделликкио
157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Попробуй это. Работал на меня.

Джениш Сахия
источник
10
Хм ... как это работает? Каждый тестовый файл будет иметь это?
Джордж Мауэр
Проблема здесь в том, если, например, A/bar.pyсуществует и у foo.pyвас есть from .bar import X.
user1834164
9
Мне пришлось удалить .. из "из ..A импорта ..." после добавления sys.path.append ("..")
Джейк OPJ
2
Если скрипт выполняется вне каталога, в котором он существует, это не сработает. Вместо этого вы должны настроить этот ответ, чтобы указать абсолютный путь к указанному сценарию .
Манавалан Гаджапати
это лучший, наименее сложный вариант
Alex R
43

Предположение:
если вы находитесь в packageкаталоге, Aи test_Aотдельные пакеты.

Вывод:
..Aимпорт разрешен только внутри пакета.

Дополнительные примечания:
Сделать относительный импорт доступным только в пакетах полезно, если вы хотите, чтобы пакеты могли быть размещены на любом пути, расположенном на sys.path.

РЕДАКТИРОВАТЬ:

Я единственный, кто думает, что это безумие !? Почему в мире текущий рабочий каталог не считается пакетом? - Мультихантер

Текущий рабочий каталог обычно находится в sys.path. Итак, все файлы там импортируются. Это поведение начиная с Python 2, когда пакеты еще не существовали. Создание действующего каталога как пакета позволило бы импортировать модули как «import .A» и как «import A», которые затем будут двумя разными модулями. Может быть, это несоответствие, чтобы рассмотреть.

пользователь
источник
86
Я единственный, кто думает, что это безумие !? Почему в мире работающий каталог не считается пакетом?
Multihunter
13
Мало того, что это безумие, это бесполезно ... так как вы тогда запускаете тесты? Понятно, о чем спрашивал ОП и почему я уверен, что здесь тоже много людей.
Джордж Мауэр
Рабочий каталог обычно находится в sys.path. Итак, все файлы там импортируются. Это поведение начиная с Python 2, когда пакеты еще не существовали. - отредактированный ответ.
Пользователь
Я не слежу за несоответствием. Поведение, python -m package.test_A.testкажется, делает то, что нужно, и я утверждаю, что это должно быть по умолчанию. Итак, вы можете привести пример несоответствия?
Multihunter
Я на самом деле думаю, есть ли запрос функции для этого? Это действительно безумие. Стиль C / C ++ #includeбыл бы очень полезен!
Николас Хамфри
29

Ни одно из этих решений не работало для меня в 3.6 с такой структурой папок, как:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Моей целью было импортировать из module1 в module2. Что, наконец, сработало для меня, было, как ни странно,

import sys
sys.path.append(".")

Обратите внимание на одну точку, в отличие от упомянутых выше двухточечных решений.


Изменить: следующее помогло прояснить это для меня:

import os
print (os.getcwd())

В моем случае рабочий каталог был (неожиданно) корневым каталогом проекта.

Джейсон Де Морроу
источник
2
он работает локально, но не работает на экземпляре aws ec2, имеет ли смысл?
thebeancounter
Это сработало и для меня - в моем случае рабочим каталогом был также корень проекта. Я использовал ярлык запуска из редактора программирования (TextMate)
JeremyDouglass
@ thebeancounter То же самое! Работает локально на моем Mac, но не работает на ec2, тогда я понял, что запускаю команду в подкаталоге на ec2 и запускаю ее с правами root. Как только я запустил его от root на ec2, это сработало.
Логан Ян
Это также работает для меня очень ценится. Из этого системного метода я теперь могу просто вызывать пакет без необходимости ".."
RamWill
sys.path.append(".")работал, потому что вы вызываете его в родительском каталоге, обратите внимание, что .всегда представляют каталог, в котором вы запускаете команду python.
KevinZhou
13

from package.A import foo

Я думаю, что это яснее, чем

import sys
sys.path.append("..")
Джо Чжоу
источник
4
это более читабельно, но все еще нужно sys.path.append(".."). протестировано на питоне 3,6
MFA
То же, что и в более старых ответах
nrofis
12

Как подсказывает самый популярный ответ, в основном это потому, что ваш PYTHONPATHили sys.pathвключает, .но не ваш путь к вашей посылке. И относительный импорт относится к вашему текущему рабочему каталогу, а не к файлу, в который происходит импорт; как ни странно.

Вы можете исправить это, сначала изменив относительный импорт на абсолютный, а затем либо начав с:

PYTHONPATH=/path/to/package python -m test_A.test

ИЛИ форсировать путь Python при вызове таким образом, потому что:

С python -m test_A.testвы выполняете test_A/test.pyс __name__ == '__main__'и__file__ == '/absolute/path/to/test_A/test.py'

Это означает, что test.pyвы можете использовать свой абсолютный importполу-защищенный в главном случае, а также выполнить одноразовые манипуляции с путями Python:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())
dlamblin
источник
8

Изменить: 2020-05-08: Кажется, что сайт, который я цитировал, больше не контролируется человеком, который написал совет, поэтому я удаляю ссылку на сайт. Спасибо, что сообщили мне знать baxx.


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

Основная цитата с сайта, который я упомянул:

«То же самое можно указать программно следующим образом:

импорт системы

sys.path.append ( '..')

Конечно, код выше должен быть написан перед другим оператором импорта .

Совершенно очевидно, что так и должно быть, думать об этом после факта. Я пытался использовать sys.path.append ('..') в своих тестах, но столкнулся с проблемой, опубликованной OP. Добавив определение import и sys.path до других моих импортов, я смог решить эту проблему.

Mierpo
источник
ссылка, которую вы разместили, мертва.
Baxx
Спасибо, что дал мне знать. Кажется, доменное имя больше не контролируется одним и тем же лицом. Я удалил ссылку.
Миерпо
5

Если у вас есть __init__.pyв верхней папке, вы можете инициализировать импорт, как import file/path as aliasв этом файле инициализации. Затем вы можете использовать его в нижних скриптах как:

import alias
Pelos
источник
0

По моему скромному мнению, я так понимаю этот вопрос:

[Случай 1] Когда вы начинаете абсолютный импорт, как

python -m test_A.test

или

import test_A.test

или

from test_A import test

вы на самом деле установка импорт-якорь , чтобы быть test_A, другими словами, пакет верхнего уровня test_A. Итак, когда у нас есть test.py do from ..A import xxx, вы убегаете от якоря, а Python не позволяет этого.

[Случай 2] Когда вы делаете

python -m package.test_A.test

или

from package.test_A import test

Ваш якорь становится package, таким package/test_A/test.pyобразом from ..A import xxx, не удаляется якорь (все еще внутри packageпапки), и Python с радостью принимает это.

Коротко:

  • Абсолютный импорт изменяет текущую привязку (= переопределяет, что является пакетом верхнего уровня);
  • Относительный импорт не меняет привязку, но ограничивает ее.

Кроме того, мы можем использовать полное имя модуля (FQMN) для проверки этой проблемы.

Проверьте FQMN в каждом случае:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Таким образом, для CASE2, результатом from .. import xxxбудет новый модуль с FQMN = package.xxx, что является приемлемым.

В то время как для CASE1, ..изнутри from .. import xxxвыпрыгнет из начального узла (якоря) test_A, и это НЕ разрешено Python.

Джимм Чен
источник
2
Это намного сложнее, чем нужно. Так много для дзен Python.
AtilioA
0

Не уверен в python 2.x, но в python 3.6, если вы пытаетесь запустить весь пакет, вы просто должны использовать -t

-t, --top-level-directory directory Каталог верхнего уровня проекта (по умолчанию это начальный каталог)

Итак, на структуре, как

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

Например, можно использовать:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

И все еще ввозить my_module.my_classбез крупных драм.

Андре де Миранда
источник