poza błędem pakietu najwyższego poziomu podczas importu względnego

316

Wygląda na to, że jest już sporo pytań na temat względnego importu w Pythonie 3, ale po przejściu wielu z nich nadal nie znalazłem odpowiedzi na mój problem. więc oto pytanie.

Mam pakiet pokazany poniżej

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

i mam jedną linię w test.py:

from ..A import foo

teraz jestem w folderze packagei biegam

python -m test_A.test

Dostałem wiadomość

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

ale jeśli jestem w folderze nadrzędnym packagenp. uruchamiam:

cd ..
python -m package.test_A.test

wszystko w porządku.

Teraz moje pytanie brzmi: kiedy jestem w folderze packagei uruchamiam moduł w sub-pakiecie test_A, ponieważ test_A.test, zgodnie z moim zrozumieniem, ..Aidzie tylko o jeden poziom, który wciąż znajduje się w packagefolderze, dlaczego wyświetla komunikat beyond top-level package. Jaki jest dokładnie powód, który powoduje ten komunikat o błędzie?

schronienie
źródło
49
ten post nie wyjaśnił mojego błędu „poza pakietem najwyższego poziomu”
shelper
4
Mam tutaj przemyślenie, więc kiedy uruchomię test_A.test jako moduł, „..” przekracza test_A, który jest już najwyższym poziomem importu test_A.test, myślę, że poziom pakietu nie jest poziomem katalogu, ale ile poziomy zaimportujesz pakiet.
schronienie
2
Obiecuję, że zrozumiesz wszystko na temat importu względnego po obejrzeniu tej odpowiedzi stackoverflow.com/a/14132912/8682868 .
pzjzeason
zobacz ValueError: podjęto próbę importu względnego poza pakietem najwyższego poziomu, aby uzyskać szczegółowe wyjaśnienie tego problemu.
napuzba
Czy istnieje sposób na uniknięcie importu względnego? Tak jak sposób, w jaki PyDev w Eclipse widzi wszystkie pakiety w <PydevProject> / src?
Mushu909,

Odpowiedzi:

172

EDYCJA: Istnieją lepsze / bardziej spójne odpowiedzi na to pytanie w innych pytaniach:


Dlaczego to nie działa? Jest tak, ponieważ python nie rejestruje, skąd pakiet został załadowany. Więc kiedy to zrobisz python -m test_A.test, po prostu odrzuca wiedzę, która test_A.testjest faktycznie przechowywana package(tzn. packageNie jest uważana za pakiet). Próba from ..A import foodostępu do informacji, których już nie ma (tj. Katalogów rodzeństwa załadowanej lokalizacji). Jest to koncepcyjnie podobne do zezwalania from ..os import pathna plik w math. Byłoby to złe, ponieważ chcesz, aby pakiety były odrębne. Jeśli muszą użyć czegoś z innego pakietu, powinni odwołać się do nich globalnie za pomocą from os import pathi pozwolić pythonowi sprawdzić, gdzie to jest za pomocą $PATHi $PYTHONPATH.

Kiedy używasz python -m package.test_A.test, wtedy używanie from ..A import foorozwiązuje się dobrze, ponieważ śledziło to, co jest w packageśrodku, a ty po prostu uzyskujesz dostęp do katalogu potomnego załadowanej lokalizacji.

Dlaczego Python nie uważa bieżącego katalogu roboczego za pakiet? Bez pojęcia , ale Boże, byłoby to użyteczne.

Multihunter
źródło
2
Zredagowałem swoją odpowiedź, aby odnieść się do lepszej odpowiedzi na pytanie, które jest równoznaczne z tym samym. Istnieją tylko obejścia. Jedyne, co faktycznie widziałem, to to, co zrobił OP, czyli użycie -mflagi i uruchomienie z katalogu powyżej.
Multihunter
1
Należy zauważyć, że ta odpowiedź , z linku podanego przez Multihuntera, nie dotyczy sys.pathwłamania, ale użycia setuptools , co moim zdaniem jest znacznie bardziej interesujące.
Angelo Cardellicchio
157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Spróbuj tego. Pracował dla mnie.

jenish Sakhiya
źródło
10
Umm ... jak to działa? Każdy pojedynczy plik testowy miałby to?
George Mauer,
Problem polega na tym, czy np. A/bar.pyIstnieje i jest w foo.pytobie from .bar import X.
user1834164,
9
Musiałem usunąć… z „z… importu…” po dodaniu sys.path.append („..”)
Jake OPJ
2
Jeśli skrypt jest wykonywany spoza katalogu, istnieje, nie działałoby. Zamiast tego musisz poprawić tę odpowiedź, aby określić bezwzględną ścieżkę do wspomnianego skryptu .
Manavalan Gajapathy
to najlepsza, najmniej skomplikowana opcja
Alex R
43

Założenie:
jeśli jesteś w packagekatalogu Ai test_Ajesteś oddzielnym pakietem.

Wniosek:
..Aimport jest dozwolony tylko w pakiecie.

Dalsze uwagi:
Udostępnianie względnych importów tylko w pakietach jest przydatne, jeśli chcesz wymusić umieszczenie pakietów na dowolnej ścieżce sys.path.

EDYTOWAĆ:

Czy jestem jedynym, który uważa, że ​​to szalone !? Dlaczego na świecie obecny katalog roboczy nie jest uważany za pakiet? - Multihunter

Bieżący katalog roboczy zwykle znajduje się w sys.path. Wszystkie pliki można więc importować. Jest to zachowanie od Python 2, gdy pakiety jeszcze nie istniały. Utworzenie działającego katalogu jako pakietu pozwoliłoby na import modułów jako „import .A” i „import A”, które wówczas byłyby dwoma różnymi modułami. Być może jest to niekonsekwencja do rozważenia.

Użytkownik
źródło
85
Czy jestem jedynym, który uważa, że ​​to szalone !? Dlaczego na świecie działający katalog nie jest uważany za pakiet?
Multihunter
13
Jest to nie tylko szalone, ale także nieprzydatne ... więc jak przeprowadzasz testy? Najwyraźniej rzecz, o którą pytał OP i dlaczego jestem pewien, że wiele osób też tu jest.
George Mauer,
Działający katalog zazwyczaj znajduje się w sys.path. Wszystkie pliki można więc importować. Jest to zachowanie od Python 2, gdy pakiety jeszcze nie istniały. - zredagowana odpowiedź.
Użytkownik
Nie przestrzegam niekonsekwencji. Zachowanie python -m package.test_A.testwydaje się robić to, co jest pożądane, a moim argumentem jest to, że powinno to być domyślne. Czy możesz mi podać przykład tej niespójności?
Multihunter
Właściwie to zastanawiam się, czy jest prośba o tę funkcję? To jest naprawdę szalone. Styl C / C ++ #includebyłby bardzo przydatny!
Nicholas Humphrey
29

Żadne z tych rozwiązań nie działało dla mnie w wersji 3.6, ze strukturą folderów taką jak:

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

Moim celem było zaimportowanie z modułu 1 do modułu 2. Dziwne, co w końcu dla mnie zadziałało:

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

Zwróć uwagę na pojedynczą kropkę w przeciwieństwie do wspomnianych dotychczas rozwiązań z dwiema kropkami.


Edycja: Poniższe pomogło mi to wyjaśnić:

import os
print (os.getcwd())

W moim przypadku katalog roboczy był (nieoczekiwanie) katalogiem głównym projektu.

Jason DeMorrow
źródło
2
działa lokalnie, ale nie działa na instancji aws ec2, czy ma to jakiś sens?
thebeancounter
To również działało dla mnie - w moim przypadku katalogiem roboczym był również katalog główny projektu. Korzystałem ze skrótu uruchamiania z edytora programowania (TextMate)
JeremyDouglass,
@thebeancounter Same! Działa lokalnie na moim komputerze Mac, ale nie działa na ec2, wtedy zdałem sobie sprawę, że uruchomiłem polecenie w podkatalogu na ec2 i uruchomiłem je lokalnie w katalogu głównym. Kiedy uruchomiłem go z roota na ec2, zadziałało.
Logan Yang,
To również działało dla mnie bardzo doceniane. Z tej metody sys mogę teraz po prostu wywołać pakiet bez potrzeby używania „..”
RamWill,
sys.path.append(".")działało, ponieważ wywołujesz go w katalogu nadrzędnym, pamiętaj, że .zawsze reprezentuje katalog, w którym uruchamiasz komendę python.
KevinZhou,
13

from package.A import foo

Myślę, że to jest jaśniejsze niż

import sys
sys.path.append("..")
Joe Zhow
źródło
4
z pewnością jest bardziej czytelny, ale wciąż potrzebuje sys.path.append(".."). testowany na Pythonie 3.6
MFA
Takie
12

Jak sugeruje najbardziej popularna odpowiedź, w zasadzie dlatego, że twoja PYTHONPATHlub sys.pathzawiera .twoją ścieżkę do pakietu. A względny import jest związany z bieżącym katalogiem roboczym, a nie plikiem, w którym odbywa się import; dziwnie.

Możesz to naprawić, najpierw zmieniając import względny na absolutny, a następnie zaczynając od:

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

LUB wymuszanie ścieżki Pythona podczas wywoływania w ten sposób, ponieważ:

Podczas python -m test_A.testwykonywania za test_A/test.pypomocą __name__ == '__main__'i__file__ == '/absolute/path/to/test_A/test.py'

Oznacza to, że test.pymożesz użyć swojego absolutnie importczęściowo chronionego w głównym przypadku, a także wykonać jednorazową manipulację ścieżką w Pythonie:

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
źródło
8

Edytuj: 2020-05-08: Wygląda na to, że strona, którą cytowałem, nie jest już kontrolowana przez osobę, która napisała poradę, więc usuwam link do strony. Dzięki za poinformowanie mnie o baxx.


Jeśli ktoś wciąż ma problemy z uzyskaniem już świetnych odpowiedzi, znalazłem porady na stronie, która nie jest już dostępna.

Niezbędny cytat ze strony, o której wspomniałem:

„To samo można określić programowo w następujący sposób:

import sys

sys.path.append ('..')

Oczywiście powyższy kod musi zostać napisany przed drugą instrukcją importu .

To oczywiste, że tak musi być, myśląc o tym po fakcie. Próbowałem użyć sys.path.append ('..') w moich testach, ale natknąłem się na problem opublikowany przez OP. Dodając definicję importu i sys.path przed innymi importami, udało mi się rozwiązać problem.

Mierpo
źródło
opublikowany link jest martwy.
baxx
Dzięki, że dałeś mi znać. Wygląda na to, że ta sama osoba nie kontroluje już nazwy domeny. Usunąłem link.
Mierpo
5

jeśli masz __init__.pyw górnym folderze, możesz zainicjować import jak import file/path as aliasw tym pliku inicjującym. Następnie możesz użyć go na niższych skryptach jako:

import alias
pelos
źródło
0

Moim skromnym zdaniem rozumiem to pytanie w następujący sposób:

[PRZYPADEK 1] Po rozpoczęciu importu bezwzględnego, takiego jak

python -m test_A.test

lub

import test_A.test

lub

from test_A import test

tak naprawdę ustawiasz kotwicę importu na test_A, innymi słowy, pakiet najwyższego poziomu to test_A. Tak więc, gdy mamy test.py from ..A import xxx, uciekasz od kotwicy, a Python nie pozwala na to.

[PRZYPADEK 2] Kiedy to zrobisz

python -m package.test_A.test

lub

from package.test_A import test

Twój kotwica staje się packagetak package/test_A/test.pyrobić from ..A import xxxnie uciec kotwicę (nadal wewnątrz packagefolderu), a Python szczęśliwie akceptuje to.

W skrócie:

  • Import bezwzględny zmienia bieżącą kotwicę (= redefiniuje, co to jest pakiet najwyższego poziomu);
  • Import względny nie zmienia kotwicy, ale ogranicza się do niej.

Ponadto możemy użyć pełnej nazwy modułu (FQMN), aby sprawdzić ten problem.

Sprawdź FQMN w każdym przypadku:

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

Tak więc, w przypadku CASE2, powstanie from .. import xxxnowy moduł z FQMN = package.xxx, co jest dopuszczalne.

Podczas gdy w przypadku CASE1, ..od wewnątrz from .. import xxxwyskoczy z węzła początkowego (kotwicy) test_Ai NIE jest to dozwolone przez Python.

Jimm Chen
źródło
2
Jest to o wiele bardziej skomplikowane, niż musi być. Tyle o Zen w Pythonie.
AtilioA
0

Nie jestem pewien w Pythonie 2.x, ale w Pythonie 3.6, zakładając, że próbujesz uruchomić cały pakiet, wystarczy użyć -t

-t, --top-level-directory katalog Katalog najwyższego poziomu projektu (domyślnie katalog startowy)

Na takiej strukturze

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

Można na przykład użyć:

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

I nadal importuj my_module.my_classbez większych dramatów.

Andre de Miranda
źródło