Jak naprawić „Próba importu względnego w pakiecie innym”, nawet jeśli __init__.py

744

Próbuję śledzić PEP 328 , o następującej strukturze katalogów:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

W core_test.pyMam następującą instrukcję import

from ..components.core import GameLoopEvents

Jednak po uruchomieniu pojawia się następujący błąd:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Podczas wyszukiwania znalazłem „ ścieżkę względną nie działa nawet z __init__.py ” i „ Importuj moduł ze ścieżki względnej ”, ale nie pomogły.

Czy czegoś tu brakuje?

skytreader
źródło
17
Byłem również bardzo zdezorientowany różnymi sposobami strukturyzacji unittestprojektów, więc napisałem ten dość wyczerpujący przykładowy projekt, który obejmuje głębokie zagnieżdżanie modułów, import względny i absolutny (tam, gdzie działa i nie działa) oraz odnośniki względne i absolutne z wewnątrz pakiet, a także import klas, pojedynczo, podwójnie i na poziomie pakietu. Pomogło jasne rzeczy do mnie!
cod3monk3y
1
Nie mogłem sprawić, by twoje testy zadziałały. Dostań, no module named myimports.fookiedy je uruchomię.
Blairg23,
@ Blairg23 Zgaduję zamierzone inwokacja jest cddo PyImportsi run python -m unittest tests.test_abs, na przykład.
duozmo
7
Zgadzam się z Gene'em. Chciałbym, aby istniał mechanizm debugowania procesu importowania, który był nieco bardziej pomocny. W moim przypadku mam dwa pliki w tym samym katalogu. Próbuję zaimportować jeden plik do drugiego. Jeśli mam plik inicjujący .py w tym katalogu, dostaję błąd ValueError: Podjęto próbę importu względnego w przypadku błędu innego niż pakiet. Jeśli usunę plik inicjujący .py, pojawi się błąd, że nie ma modułu o nazwie „NAZWA”.
user1928764
W moim przypadku mam dwa pliki w tym samym katalogu. Próbuję zaimportować jeden plik do drugiego. Jeśli mam plik inicjujący .py w tym katalogu, dostaję błąd ValueError: Podjęto próbę importu względnego w przypadku błędu innego niż pakiet. Jeśli usunę plik inicjujący .py, pojawi się błąd, że nie ma modułu o nazwie „NAZWA”. Naprawdę frustrujące jest to, że miałem to działające, a następnie postrzeliłem się w stopę, usuwając plik .bashrc, który ustawił PYTHONPATH na coś, a teraz nie działa.
user1928764

Odpowiedzi:

443

Tak. Nie używasz go jako pakietu.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
źródło
51
Gotcha: Zauważ, że na końcu nie ma „.py”!
złodziej myśli 7.12
497
Nie jestem żadnym z downvoters, ale wydaje mi się, że przydałoby się to nieco więcej szczegółów, biorąc pod uwagę popularność tego pytania i odpowiedzi. Zauważając rzeczy takie jak z jakiego katalogu wykonać powyższą komendę powłoki, fakt, że potrzebujesz __init__.pys aż do końca, i __package__sztuczka modyfikująca (opisana poniżej przez BrenBarn) musiała umożliwić te importowanie skryptów wykonywalnych (np. Przy użyciu shebang i ./my_script.pyprzydałoby się to w powłoce uniksowej). Cały ten problem był dla mnie dość trudny do znalezienia lub znalezienia zwięzłej i zrozumiałej dokumentacji.
Mark Amery
16
Uwaga: musisz znajdować się poza katalogiem pkgw punkcie, w którym wywołujesz tę linię z CLI. Następnie powinno działać zgodnie z oczekiwaniami. Jeśli jesteś w środku pkgi dzwonisz python -m tests.core_test, to nie zadziała. Przynajmniej nie dla mnie.
Blairg23,
94
Poważnie, czy możesz wyjaśnić, co się dzieje w odpowiedzi?
Pinokio
18
@ MarkAmery Prawie straciłem rozum, próbując zrozumieć, jak to wszystko działa, względny import w ramach projektu z podkatalogami z plikami py, które mają __init__.pypliki, ale wciąż pojawia się ValueError: Attempted relative import in non-packagebłąd. Zapłaciłbym naprawdę dobre pieniądze komuś, gdzieś, by w końcu wyjaśnić prostym językiem angielskim, jak to wszystko działa.
AdjunctProfessorFalcon
635

Aby rozwinąć odpowiedź Ignacio Vazquez-Abramsa :

Mechanizm importu Python działa w stosunku do __name__bieżącego pliku. Gdy plik jest wykonywany bezpośrednio, nie ma on swojej zwykłej nazwy, ale "__main__"zamiast tego ma swoją nazwę. Dlatego import względny nie działa.

Możesz, jak zasugerował Igancio, wykonać go za pomocą -mopcji. Jeśli masz część pakietu, która ma być uruchamiana jako skrypt, możesz również użyć tego __package__atrybutu, aby powiedzieć temu plikowi, jaką ma mieć nazwę w hierarchii pakietów.

Szczegółowe informacje można znaleźć na stronie http://www.python.org/dev/peps/pep-0366/

BrenBarn
źródło
55
Zajęło mi trochę czasu, zanim zdałem sobie sprawę, że nie możesz biegać python -m core_testz testspodkatalogu - musi to być od rodzica lub musisz dodać rodzica do ścieżki.
Aram Kocharyan
3
@DannyStaple: Niezupełnie. Możesz użyć __package__tego, aby pliki wykonywalne skryptów mogły względnie importować inne moduły z tego samego pakietu. Nie ma sposobu na względny import z „całego systemu”. Nie jestem nawet pewien, dlaczego chcesz to zrobić.
BrenBarn
2
Mam na myśli, że jeśli __package__symbol jest ustawiony na „parent.child”, to możesz zaimportować „parent.other_child”. Być może nie powiedziałem tego zbyt dobrze.
Danny Staple,
5
@DannyStaple: Cóż, jak to działa, opisano w dołączonej dokumentacji. Jeśli masz skrypt script.pyw pakiecie pack.subpack, a następnie ustawienie to __package__aby pack.subpackpozwoli Ci zrobić from ..module import something, aby coś z importu pack.module. Pamiętaj, że zgodnie z dokumentacją nadal musisz mieć pakiet najwyższego poziomu na ścieżce systemowej. W ten sposób działają już importowane moduły. Jedyne, co można __package__zrobić, to zezwolić na użycie tego zachowania również do skryptów wykonywanych bezpośrednio.
BrenBarn
3
Używam __package__w skrypcie, który jest wykonywany bezpośrednio, ale niestety pojawia się następujący błąd: „Moduł macierzysty„ xxx ”nie został załadowany, nie może wykonać importu względnego”
mononoke,
202

Możesz użyć import components.corebezpośrednio, jeśli dołączysz bieżący katalog do sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
ihm
źródło
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))to też zadziała
dnia
26
from os import syswygląda jak oszustwo :)
latające owce
3
@Piotr: Można to uznać za lepsze, ponieważ nieco lepiej pokazuje, co jest dołączane sys.path- element nadrzędny katalogu, w którym znajduje się bieżący plik.
martineau
8
@flyingsheep: Zgadzam się, po prostu użyję zwykłego import sys, os.path as path.
martineau,
10
FYI, aby wykorzystać to w notatniku ipython, ja dostosować tę odpowiedź: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Wtedy import components.coredziała dla mnie prosta , importując z katalogu nadrzędnego notebooka według potrzeb.
Racing Tadpole
195

To zależy od tego, jak chcesz uruchomić skrypt.

Jeśli chcesz uruchomić swój test UnitTest z wiersza poleceń w klasyczny sposób, to znaczy:

python tests/core_test.py

Następnie, ponieważ w tym przypadku „elementy” i „testy” są rodzeństwem foldery, można importować względnego modułu albo stosując wkładkę lub append sposobu sys.path modułu. Coś jak:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

W przeciwnym razie możesz uruchomić skrypt za pomocą argumentu „-m” (zwróć uwagę, że w tym przypadku mówimy o pakiecie, a zatem nie możesz podać rozszerzenia „.py” ), to znaczy:

python -m pkg.tests.core_test

W takim przypadku możesz po prostu użyć importu względnego, tak jak robiłeś:

from ..components.core import GameLoopEvents

Możesz w końcu wymieszać oba podejścia, aby skrypt działał bez względu na to, jak się nazywa. Na przykład:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Paolo Rovelli
źródło
3
co powinienem zrobić, jeśli próbuję użyć pdb do debugowania? ponieważ używasz python -m pdb myscript.pydo uruchomienia sesji debugowania.
danny
1
@dannynjust - To dobre pytanie, ponieważ nie możesz mieć 2 głównych modułów. Ogólnie podczas debugowania wolę ręcznie wpaść do debugera w pierwszym punkcie, w którym chcę rozpocząć debugowanie. Możesz to zrobić, wstawiając a import pdb; pdb.set_trace()do kodu (wbudowany).
mgilson,
3
Czy lepiej jest używać insertzamiast append? Oznacza to, żesys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
Zastosowanie wstawiania lepiej pasuje do względnej semantyki importu, w której lokalne nazwy pakietów mają pierwszeństwo przed zainstalowanymi pakietami. Zwłaszcza w przypadku testów zwykle chcesz przetestować wersję lokalną, a nie zainstalowaną (chyba że twoja infrastruktura testowa instaluje testowany kod, w którym to przypadku importowanie względne nie jest potrzebne i nie będziesz miał tego problemu).
Alex Dupuy,
1
powinieneś także wspomnieć, że nie możesz znajdować się w katalogu zawierającym test_rdzenia, gdy uruchamiasz się jako moduł (byłoby to zbyt łatwe)
Joseph Garvin,
25

W core_test.py wykonaj następujące czynności:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
źródło
10

Jeśli Twój przypadek użycia służy do uruchamiania testów i wydaje się, że tak jest, możesz wykonać następujące czynności. Zamiast uruchamiać skrypt testowy, python core_test.pyużyj środowiska testowego, takiego jak pytest. Następnie w wierszu poleceń możesz wprowadzić

$$ py.test

To uruchomi testy w twoim katalogu. To omija kwestię __name__bycia __main__wskazaną przez @BrenBarn. Następnie umieść pusty __init__.pyplik w katalogu testowym, dzięki czemu katalog testowy stanie się częścią pakietu. Wtedy będziesz w stanie to zrobić

from ..components.core import GameLoopEvents

Jeśli jednak uruchomisz skrypt testowy jako program główny, sytuacja znów się nie powiedzie. Więc po prostu użyj testera. Może działa to również z innymi testerami, takimi jak, nosetestsale nie sprawdziłem tego. Mam nadzieję że to pomoże.

deepak
źródło
9

Moją szybką poprawką jest dodanie katalogu do ścieżki:

import sys
sys.path.insert(0, '../components/')
v4gil
źródło
6
Twoje podejście nie zadziała we wszystkich przypadkach, ponieważ część „../” jest rozwiązana z katalogu, z którego uruchamiasz skrypt (core_test.py). Zgodnie z twoim podejściem, musisz uruchomić cd do „testów” przed uruchomieniem scritp core_test.py.
xyman
7

Problem dotyczy twojej metody testowania,

próbowałeś python core_test.py

wtedy pojawi się ten błąd ValueError: Próba importu względnego w pakiecie innym niż pakiet

Powód: testujesz swoje opakowanie ze źródła innego niż pakiet.

więc przetestuj moduł ze źródła pakietu.

jeśli to jest struktura twojego projektu,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

lub z zewnątrz pkg /

python -m pkg.tests.core_test

pojedynczy, .jeśli chcesz importować z folderu w tym samym katalogu. dla każdego kroku wstecz dodaj jeszcze jeden.

hi/
  hello.py
how.py

w how.py

from .hi import hello

incase, jeśli chcesz zaimportować jak z hello.py

from .. import how
Mohideen bin Mohammed
źródło
1
Lepsza niż zaakceptowana odpowiedź
GabrielBB
W tym przykładzie from .. import how, w jaki sposób importujesz określoną klasę / metodę z pliku „how”. kiedy robię ekwiwalent from ..how import foowtedy otrzymuję „próbę względnego importu poza pakiet najwyższego poziomu”
James Hulse
3

Stary wątek Dowiedziałem się, że dodanie __all__= ['submodule', ...]do pliku __init__.py , a następnie użycie from <CURRENT_MODULE> import *w celu działa dobrze.

Laurent
źródło
3

Możesz użyć from pkg.components.core import GameLoopEvents, na przykład, używam pycharm, poniżej jest obraz struktury mojego projektu, po prostu importuję z pakietu głównego, a następnie działa:

wprowadź opis zdjęcia tutaj

Jayhello
źródło
3
To mi nie zadziałało. Czy musiałeś ustawić ścieżkę w swojej konfiguracji?
Mohammad Mahjoub
3

Jak powiedział Paolo , mamy 2 metody wywoływania:

1) python -m tests.core_test
2) python tests/core_test.py

Jedną różnicą między nimi jest ciąg sys.path [0]. Ponieważ interpretacja przeszukuje sys.path podczas importowania , możemy zrobić z tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

I więcej po tym, możemy uruchomić core_test.py innymi metodami:

cd tests
python core_test.py
python -m core_test
...

Uwaga, testowany tylko py36.

zhengcao
źródło
3

To podejście działało dla mnie i jest mniej zagracone niż niektóre rozwiązania:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

Katalog nadrzędny znajduje się w mojej PYTHONPATH, aw __init__.pykatalogu nadrzędnym i tym katalogu znajdują się pliki.

Powyższe zawsze działało w Pythonie 2, ale Python 3 czasami uderzał w ImportError lub ModuleNotFoundError (ten ostatni jest nowy w Pythonie 3.6 i podklasie ImportError), więc następująca poprawka działa dla mnie zarówno w Pythonie 2, jak i 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
źródło
2

Spróbuj tego

import components
from components import *
Vaishnavi Bala
źródło
1

Jeśli ktoś szuka obejścia, natknąłem się na jedno. Oto trochę kontekstu. Chciałem przetestować jedną z metod, które mam w pliku. Kiedy uruchomię to od wewnątrz

if __name__ == "__main__":

zawsze skarżyło się na względny przywóz. Próbowałem zastosować powyższe rozwiązania, ale nie działałem, ponieważ było wiele zagnieżdżonych plików, każdy z wieloma importami.

Oto co zrobiłem. Właśnie stworzyłem program uruchamiający, zewnętrzny program, który importuje niezbędne metody i wywołuje je. Chociaż nie jest to świetne rozwiązanie, działa.

HappyWaters
źródło
0

Oto jeden sposób, który wkurzy wszystkich, ale zadziała całkiem nieźle. W trakcie testów:

ln -s ../components components

Następnie po prostu zaimportuj komponenty, jak zwykle.

SteveCalifornia
źródło
0

Jest to bardzo mylące, a jeśli używasz IDE jak pycharm, jest to trochę bardziej mylące. Co działało dla mnie: 1. Wprowadź ustawienia projektu pycharm (jeśli uruchamiasz Pythona z VE lub z katalogu python) 2. Nie ma nic złego w tym, co zdefiniowałeś. czasami współpracuje z klasą importu folder1.file1

jeśli to nie działa, użyj import folder1.file1 3. Zmienna środowiskowa powinna być poprawnie wymieniona w systemie lub podać ją w argumencie wiersza poleceń.

SANTI SANTOSH MAHAPATRA
źródło
-2

Ponieważ Twój kod zawiera elementy if __name__ == "__main__", których nie można zaimportować jako pakiet, lepiej użyj sys.path.append()tego rozwiązania.

rosefun
źródło
Nie sądzę, aby posiadanie if __name__ == "__main__"w twoim pliku miało wpływ na cokolwiek związanego z importowaniem.
user48956