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


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. В чем именно причина этого сообщения об ошибке?


49
в этом посте не объяснялась моя ошибка «за пределами пакета верхнего уровня»
shelper

4
У меня есть мысль, поэтому, когда при запуске test_A.test в качестве модуля, «..» идет выше test_A, который уже является высшим уровнем импорта test_A.test, я думаю, что уровень пакета не уровень каталога, а сколько Уровни вы импортируете пакет.
Шелпер

2
Обещаю, что вы поймете все об относительном импорте после просмотра этого ответа stackoverflow.com/a/14132912/8682868 .
pzjzeason


Есть ли способ избежать относительного импорта? Например, как 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 не считает текущий рабочий каталог пакетом? НЕТ КЛИПА , но черт возьми, это было бы полезно.


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())

8

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


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

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

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

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

sys.path.append ( '..')

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

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


ссылка, которую вы разместили, мертва.
Baxx

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

5

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

import alias

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без крупных драм.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.