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


316

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

4
У мене тут є думка, тож коли запустити test_A.test як модуль, '..' переходить вище test_A, що вже є найвищим рівнем імпорту test_A.test, я думаю, що рівень пакету - це не рівень каталогу, а скільки рівні, коли ви імпортуєте пакет.
укриття

2
Обіцяю, що ви зрозумієте все про відносний імпорт, переглянувши цю відповідь stackoverflow.com/a/14132912/8682868 .
pzjzeason

див. ValueError: спроба відносного імпорту за межі пакету вищого рівня для детального пояснення цієї проблеми.
napuzba

Чи є спосіб уникнути відносного імпорту? Наприклад, як PyDev в Eclipse бачить усі пакети в <PydevProject> / src?
Mushu909

Відповіді:


172

EDIT: На це питання є кращі / узгодженіші відповіді в інших питаннях:


Чому це не працює? Це тому, що 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
Я відредагував свою відповідь, щоб послатись на кращу відповідь на питання, яке означає те саме. Є лише обхідні шляхи. Єдине, що я насправді бачив у роботі - це те, що зробила ОП, а це використовувати -mпрапор і запустити з каталогу, наведеного вище.
Multihunter

1
Слід зазначити, що ця відповідь за посиланням, наданим Multihunter, передбачає не sys.pathхак, а використання setuptools , що на мою думку набагато цікавіше.
Angelo

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Спробуйте це. Працювали для мене.


10
Гм ... як би ця робота була? Кожен тестовий файл мав би це?
Джордж Мауер

Проблема тут полягає в тому, якщо, наприклад, A/bar.pyіснує і у foo.pyвас from .bar import X.
користувач1834164

9
Мені довелося видалити .. "з" імпортувати ... "після додавання sys.path.append (" .. ")
Джейк OPJ

2
Якщо сценарій виконується за межами каталогу, який він існує, це не працює. Натомість вам потрібно змінити цю відповідь, щоб вказати абсолютний шлях вказаного сценарію .
Manavalan Gajapathy

це найкращий, найменш складний варіант
Alex R

43

Припущення:
якщо ви знаходитесь в packageкаталозі, Aі test_Aце окремі пакети.

Висновок:
..Aімпорт дозволений лише в межах пакету.

Подальші зауваження:
Надання відносного імпорту доступним лише для пакетів корисно, якщо ви хочете змусити їх розміщувати на будь-якому шляху, розташованому на sys.path.

Редагувати:

Я єдиний, хто думає, що це божевільно !? Чому в світі поточний робочий каталог не вважається пакетом? - Мультихонтер

Поточний робочий каталог зазвичай розташований у sys.path. Отже, всі файли там є важливими. Це поведінка з Python 2, коли пакетів ще не було. Створення пакету запущеної директорії дозволить імпортувати модулі як "імпорт. Можливо, це невідповідність для розгляду.


85
Я єдиний, хто думає, що це божевільно !? Чому у світі запущений каталог не вважається пакетом?
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, він працював.
Логан Ян

Це також працювало, щоб мене дуже цінували. З цього методу sys я зараз можу просто зателефонувати в пакет без необхідності ".."
RamWill

sys.path.append(".")працював, тому що ви викликаєте його в батьківському каталозі. Зверніть увагу, що .завжди представляють каталог, у якому запускаєте команду python.
KevinZhou

13

from package.A import foo

Я думаю, що це зрозуміліше ніж

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

4
він читає напевно, але все ж потребує sys.path.append(".."). випробуваний на python 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: Здається, веб-сайт, який я цитував, більше не контролюється особою, яка написала поради, тому я видаляю посилання на сайт. Дякуємо, що повідомили мені, що бах.


Якщо хтось усе ще трохи бореться після вже наданих чудових відповідей, я знайшов поради на веб-сайті, який більше недоступний.

Основна цитата сайту, про який я згадав:

"Те саме можна задати програмно таким чином:

імпортувати sys

sys.path.append ('..')

Звичайно, код вище повинен бути записаний перед іншим заявою про імпорт .

Цілком очевидно, що так має бути, думаючи про це після факту. Я намагався використовувати sys.path.append ('..') у своїх тестах, але зіткнувся з проблемою, розміщеною ОП. Додавши визначення import та sys.path до мого іншого імпорту, я зміг вирішити проблему.


посилання, яке ви опублікували, мертве.
baxx

Дякую, що повідомив. Здається, доменне ім’я вже не контролюється однією і тією ж особою. Я видалив посилання.
Mierpo

5

якщо у вас є __init__.pyу верхній папці, ви можете ініціалізувати імпорт як import file/path as aliasу цьому файлі init. Тоді ви можете використовувати його на нижчих сценаріях як:

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, a from .. import xxxпризведе до нового модуля з FQMN = package.xxx, що є прийнятним.

У той час як для CASE1, ..зсередини from .. import xxxвискочить із початкового вузла (якоря) test_A, і Python цього НЕ дозволений.


2
Це спосіб складніший, ніж це має бути. Стільки для Дзен Пітона.
AtilioA

0

Не впевнений у python 2.x, але в python 3.6, якщо припустити, що ви намагаєтеся запустити весь набір, просто потрібно використовувати -t

-t, - каталог директорій верхнього рівня в каталозі верхнього рівня проекту (за замовчуванням для запуску каталогу)

Так, на структуру, як

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.