6

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

11

У меня возникла проблема с относительными импортами в 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. Почему тогда возникает сообщение о том, что импорт происходит «за пределами верхнего уровня пакета»? В чем именно причина возникновения этого сообщения об ошибке?

5 ответ(ов)

2

Вы можете использовать следующий код для добавления родительского каталога в путь к модулям Python:

import sys
sys.path.append("..")  # Добавляет верхний каталог в путь к модулям.

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

0

Проблема, с которой вы столкнулись, типична при работе с многопакетными структурами в Python. Попробуем разобраться, как правильно импортировать модули, учитывая вашу структуру каталогов:

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

Для того чтобы импортировать module1 в module2, вы можете использовать следующий код:

from package1.subpackage1 import module1

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

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

Дело в том, что точка (".") добавляет корневую директорию проекта в путь поиска модулей, что может решить вашу проблему с импортом.

Обратите внимание, что это немного отличается от решений, которые пытались использовать путь с двумя точками (".."), так как они указывают на уровень выше текущей директории. В вашем случае, похоже, рабочая директория неожиданно оказалась корнем проекта, что объясняет, почему добавление точки сработало.

На всякий случай, вот как вы можете проверить вашу текущую рабочую директорию:

import os
print(os.getcwd())

Это поможет понять, откуда именно Python пытается импортировать ваши модули. Если рабочая директория — это корень проекта, то ваш способ добавления текущего каталога в sys.path действительно является подходящим решением.

0

В контексте Python, конструкция from package.A import foo позволяет явно импортировать функцию или класс foo из модуля A, находящегося в пакете package. Это, как правило, более читаемо и понятно, чем использование:

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

В приведённом втором варианте мы сначала изменяем системный путь, добавляя родительскую директорию, а затем необходимо будет использовать import для подключения модуля. Такой подход может быть менее прозрачным, так как увеличивает вероятность ошибок и усложняет понимание структуры проекта.

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

0

Как указывает наиболее популярный ответ, причина в том, что ваш 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, защищенный в условии if __name__ == '__main__', а также выполнить однократную манипуляцию с путями 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())

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

0

На самом деле, это гораздо проще, чем предполагают другие ответы.

Кратко: Импортируйте A напрямую вместо того, чтобы пытаться использовать относительный импорт.

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

Не меняя ничего в структуре каталогов, вам нужно лишь изменить способ импорта в test.py из foo.py.

from A import foo

Теперь, запустив команду python -m test_A.test из директории package, вы сможете выполнить её без ImportError.

Почему это работает?

Ваша текущая рабочая директория не является пакетом, но она добавляется в путь. Таким образом, вы можете напрямую импортировать папку A и её содержимое. Это то же самое, что и импортировать любой другой пакет, который вы установили... они все включены в ваш путь.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь