So beheben Sie "Versuchter relativer Import in Nicht-Paket" auch mit __init__.py

744

Ich versuche, PEP 328 mit der folgenden Verzeichnisstruktur zu folgen :

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

In habe core_test.pyich die folgende Importanweisung

from ..components.core import GameLoopEvents

Beim Ausführen wird jedoch der folgende Fehler angezeigt:

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

Beim Durchsuchen fand ich " relativer Pfad funktioniert nicht einmal mit __init__.py " und " Importiere ein Modul von einem relativen Pfad ", aber sie halfen nicht.

Fehlt mir hier etwas?

Skytreader
quelle
17
Ich war auch sehr verwirrt über die verschiedenen Arten der Strukturierung von unittestProjekten, daher schrieb ich dieses ziemlich erschöpfende Beispielprojekt , das die tiefe Verschachtelung von Modulen, relative und absolute Importe (wo die Arbeit funktioniert und nicht) sowie relative und absolute Referenzierung aus einem heraus behandelt Paket sowie Import von Klassen auf Einzel-, Doppel- und Paketebene. Hat mir geholfen, die Dinge richtig zu machen!
cod3monk3y
1
Ich konnte Ihre Tests nicht zum Laufen bringen. Immer weiter, no module named myimports.foowenn ich sie laufen lasse.
Blairg23
@ Blairg23 Ich vermute , die beabsichtigte Anrufung ist cdin PyImports, und laufen python -m unittest tests.test_abs, zum Beispiel.
Duozmo
7
Ich stimme Gene zu. Ich wünschte, es gäbe einen Mechanismus zum Debuggen des Importprozesses, der etwas hilfreicher wäre. In meinem Fall habe ich zwei Dateien im selben Verzeichnis. Ich versuche, eine Datei in die andere Datei zu importieren. Wenn sich in diesem Verzeichnis eine init .py-Datei befindet, wird ein ValueError: Relativer Importversuch bei Nicht-Paket-Fehler angezeigt. Wenn ich die init .py-Datei entferne , erhalte ich den Fehler no Modul namens 'NAME'.
user1928764
In meinem Fall habe ich zwei Dateien im selben Verzeichnis. Ich versuche, eine Datei in die andere Datei zu importieren. Wenn sich in diesem Verzeichnis eine init .py-Datei befindet, wird ein ValueError: Relativer Importversuch bei Nicht-Paket-Fehler angezeigt. Wenn ich die init .py-Datei entferne , erhalte ich den Fehler no Modul namens 'NAME'. Was wirklich frustrierend ist, ist, dass ich diese Funktion hatte und mich dann in den Fuß geschossen habe, indem ich die .bashrc-Datei gelöscht habe, die den PYTHONPATH auf etwas gesetzt hat, und jetzt funktioniert es nicht mehr.
user1928764

Antworten:

443

Ja. Sie verwenden es nicht als Paket.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
quelle
51
A gotcha: Beachten Sie, dass am Ende kein '.py' steht!
Mindthief
497
Ich bin keiner der Downvoter, aber ich bin der Meinung, dass dies angesichts der Popularität dieser Frage und Antwort einiges detaillierter sein könnte . Beachten Sie Dinge wie das Verzeichnis, in dem der obige Shell-Befehl ausgeführt werden soll, die Tatsache, dass Sie __init__.pys ganz unten benötigen , und den __package__Trick zum Ändern (unten von BrenBarn beschrieben), der erforderlich ist, um diese Importe für ausführbare Skripte zu ermöglichen (z. B. bei Verwendung eines shebang und ./my_script.pyan der Unix-Shell zu tun ) wäre alles nützlich. Diese ganze Ausgabe war ziemlich schwierig für mich, eine präzise und verständliche Dokumentation zu finden oder zu finden.
Mark Amery
16
Hinweis: Sie müssen sich pkgan dem Punkt außerhalb des Verzeichnisses befinden, an dem Sie diese Zeile über die CLI aufrufen. Dann sollte es wie erwartet funktionieren. Wenn Sie drinnen sind pkgund anrufen python -m tests.core_test, funktioniert es nicht. Zumindest nicht für mich.
Blairg23
94
Können Sie im Ernst erklären, was in Ihrer Antwort vor sich geht?
Pinocchio
18
@MarkAmery Ich habe fast den Verstand verloren, als ich versuchte herauszufinden, wie das alles funktioniert. Relative Importe innerhalb eines Projekts mit Unterverzeichnissen mit py-Dateien, die __init__.pyDateien enthalten, aber Sie erhalten immer wieder den ValueError: Attempted relative import in non-packageFehler. Ich würde irgendwo wirklich gutes Geld für jemanden bezahlen, um endlich im Klartext zu erklären, wie das alles funktioniert.
AdjunctProfessorFalcon
635

Um die Antwort von Ignacio Vazquez-Abrams näher zu erläutern :

Der Python-Importmechanismus funktioniert relativ zur __name__aktuellen Datei. Wenn Sie eine Datei direkt ausführen, hat sie nicht den üblichen Namen, sondern "__main__"den Namen. Relative Importe funktionieren also nicht.

Sie können es, wie von Igancio vorgeschlagen, mit der -mOption ausführen . Wenn Sie einen Teil Ihres Pakets haben, der als Skript ausgeführt werden soll, können Sie das __package__Attribut auch verwenden , um dieser Datei mitzuteilen, welchen Namen sie in der Pakethierarchie haben soll.

Weitere Informationen finden Sie unter http://www.python.org/dev/peps/pep-0366/ .

BrenBarn
quelle
55
Es hat eine Weile gedauert, bis mir klar wurde, dass Sie nicht python -m core_testaus dem testsUnterverzeichnis heraus ausgeführt werden können - es muss vom übergeordneten Verzeichnis stammen oder Sie müssen das übergeordnete Element zum Pfad hinzufügen.
Aram Kocharyan
3
@DannyStaple: Nicht genau. Sie können __package__damit sicherstellen, dass ausführbare Skriptdateien andere Module aus demselben Paket relativ importieren können. Es gibt keine Möglichkeit, relativ aus "dem gesamten System" zu importieren. Ich bin mir nicht mal sicher, warum Sie das tun möchten.
BrenBarn
2
Ich meine, wenn das __package__Symbol auf "parent.child" gesetzt ist, können Sie "parent.other_child" importieren. Vielleicht habe ich es nicht so gut formuliert.
Danny Staple
5
@DannyStaple: Nun, wie es funktioniert, ist in der verlinkten Dokumentation beschrieben. Wenn Sie ein Skript script.pyim Paket pack.subpack, dann Einstellung ist es __package__zu was pack.subpackSie tun können , from ..module import somethingum zu importieren etwas pack.module. Beachten Sie, dass Sie, wie in der Dokumentation angegeben, immer noch das Paket der obersten Ebene im Systempfad haben müssen. So funktioniert es bereits bei importierten Modulen. Das einzige , was __package__tut , ist man dieses Verhalten als auch für direkt ausgeführte Skripte verwenden lassen.
BrenBarn
3
Ich verwende __package__in dem Skript, das direkt ausgeführt wird, aber leider erhalte ich die folgende Fehlermeldung: "Übergeordnetes Modul 'xxx' nicht geladen, kann keinen relativen Import durchführen"
Mononoke
202

Sie können import components.coredirekt verwenden, wenn Sie das aktuelle Verzeichnis an Folgendes anhängen 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
quelle
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))Dies wird auch funktionieren
ajay
26
from os import syssieht aus wie Betrug :)
fliegende Schafe
3
@Piotr: Es könnte als besser angesehen werden, weil es etwas deutlicher zeigt, an was angehängt wird sys.path- das übergeordnete Verzeichnis des Verzeichnisses, in dem sich die aktuelle Datei befindet.
Martineau
8
@flyingsheep: Einverstanden, ich würde nur einen regulären verwenden import sys, os.path as path.
Martineau
10
Zu Ihrer Information, um dies in einem Ipython-Notizbuch zu verwenden, habe ich diese Antwort angepasst an : import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Dann import components.corefunktioniert ein Straight für mich und importiert wie gewünscht aus dem übergeordneten Verzeichnis des Notebooks.
Racing Tadpole
195

Dies hängt davon ab, wie Sie Ihr Skript starten möchten.

Wenn Sie Ihren UnitTest auf klassische Weise über die Befehlszeile starten möchten, gilt Folgendes :

python tests/core_test.py

Da in diesem Fall 'Komponenten' und 'Tests' Geschwisterordner sind, können Sie das relative Modul entweder mit der Einfügemethode oder der Append- Methode des Moduls sys.path importieren . Etwas wie:

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

Andernfalls können Sie Ihr Skript mit dem Argument '-m' starten (beachten Sie, dass es sich in diesem Fall um ein Paket handelt und Sie daher nicht die Erweiterung '.py' angeben dürfen ).

python -m pkg.tests.core_test

In einem solchen Fall können Sie einfach den relativen Import verwenden, wie Sie es getan haben:

from ..components.core import GameLoopEvents

Sie können schließlich die beiden Ansätze mischen, sodass Ihr Skript unabhängig vom Namen funktioniert. Zum Beispiel:

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
quelle
3
Was soll ich tun, wenn ich versuche, die PDF zum Debuggen zu verwenden? da Sie verwenden python -m pdb myscript.py, um die Debugging-Sitzung zu starten.
Danny
1
@dannynjust - Das ist eine gute Frage, da Sie nicht 2 Hauptmodule haben können. Im Allgemeinen ziehe ich es beim Debuggen vor, an dem ersten Punkt, an dem ich mit dem Debuggen beginnen möchte, manuell in den Debugger zu wechseln. Sie können dies tun, indem Sie ein import pdb; pdb.set_trace()in den Code einfügen (Inline).
mgilson
3
Ist es besser zu verwenden insertals append? Das heißt,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
Die Verwendung von insert passt besser zur relativen Importsemantik, bei der lokale Paketnamen Vorrang vor installierten Paketen haben. Insbesondere für Tests möchten Sie normalerweise die lokale Version testen, nicht die installierte (es sei denn, Ihre Testinfrastruktur installiert den zu testenden Code. In diesem Fall sind relative Importe nicht erforderlich und Sie haben dieses Problem nicht).
Alex Dupuy
1
Sie sollten auch erwähnen, dass Sie nicht in dem Verzeichnis sein können, das core_test enthält, wenn Sie als Modul ausgeführt werden (das wäre zu einfach)
Joseph Garvin
25

Gehen Sie in core_test.py wie folgt vor:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
quelle
10

Wenn Ihr Anwendungsfall für das Ausführen von Tests vorgesehen ist und dies der Fall ist, können Sie Folgendes tun. Anstatt Ihr Testskript so auszuführen, python core_test.pyverwenden Sie ein Testframework wie z pytest. Dann können Sie in der Befehlszeile eingeben

$$ py.test

Dadurch werden die Tests in Ihrem Verzeichnis ausgeführt. Dies umgeht das Problem des __name__Seins __main__, auf das @BrenBarn hingewiesen hat. Fügen Sie als Nächstes eine leere __init__.pyDatei in Ihr Testverzeichnis ein. Dadurch wird das Testverzeichnis Teil Ihres Pakets. Dann können Sie tun

from ..components.core import GameLoopEvents

Wenn Sie Ihr Testskript jedoch als Hauptprogramm ausführen, schlagen die Dinge erneut fehl. Verwenden Sie also einfach den Testläufer. Vielleicht funktioniert das auch mit anderen Testläufern wie, nosetestsaber ich habe es nicht überprüft. Hoffe das hilft.

deepak
quelle
9

Meine schnelle Lösung besteht darin, das Verzeichnis dem Pfad hinzuzufügen:

import sys
sys.path.insert(0, '../components/')
v4gil
quelle
6
Ihr Ansatz funktioniert nicht in allen Fällen, da der Teil '../' aus dem Verzeichnis aufgelöst wird, in dem Sie Ihr Skript ausführen (core_test.py). Bei Ihrem Ansatz sind Sie gezwungen, eine CD mit 'tests' zu erstellen, bevor Sie den scritp core_test.py ausführen.
XYMAN
7

Problem ist mit Ihrer Testmethode,

du versuchtest python core_test.py

Dann wird dieser Fehler angezeigt. ValueError: Relativer Importversuch in Nicht-Paket versucht

Grund: Sie testen Ihre Verpackung aus einer nicht verpackten Quelle.

Testen Sie also Ihr Modul aus der Paketquelle.

Wenn dies Ihre Projektstruktur ist,

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

cd pkg

python -m tests.core_test # dont use .py

oder von außerhalb pkg /

python -m pkg.tests.core_test

single, .wenn Sie aus einem Ordner im selben Verzeichnis importieren möchten. Fügen Sie für jeden Schritt zurück einen weiteren hinzu.

hi/
  hello.py
how.py

im how.py

from .hi import hello

falls Sie wie aus hello.py importieren möchten

from .. import how
Mohideen bin Mohammed
quelle
1
Besser als akzeptierte Antwort
GabrielBB
from .. import howWie importieren Sie im Beispiel eine bestimmte Klasse / Methode aus der 'how'-Datei? Wenn ich das Äquivalent dazu mache, from ..how import foobekomme ich "einen relativen Importversuch über das Top-Level-Paket hinaus"
James Hulse
3

Alter Faden. Ich fand heraus, dass das Hinzufügen eines __all__= ['submodule', ...]zur __init__.py- Datei und das anschließende Verwenden von from <CURRENT_MODULE> import *im Ziel gut funktioniert.

Laurent
quelle
3

Sie können from pkg.components.core import GameLoopEventszum Beispiel pycharm verwenden. Das Folgende ist mein Projektstrukturbild. Ich importiere es einfach aus dem Root-Paket, dann funktioniert es:

Geben Sie hier die Bildbeschreibung ein

Jayhello
quelle
3
Das hat bei mir nicht funktioniert. Mussten Sie den Pfad in Ihrer Konfiguration festlegen?
Mohammad Mahjoub
3

Wie Paolo sagte, haben wir zwei Aufrufmethoden:

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

Ein Unterschied zwischen ihnen ist die Zeichenfolge sys.path [0]. Da die Interpretation beim Importieren nach sys.path sucht , können wir Folgendes tun 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>

Und danach können wir core_test.py mit anderen Methoden ausführen:

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

Beachten Sie, dass nur py36 getestet wurde.

Zhengcao
quelle
3

Dieser Ansatz hat bei mir funktioniert und ist weniger überladen als einige Lösungen:

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

Das übergeordnete Verzeichnis befindet sich in meinem PYTHONPATH, und __init__.pyim übergeordneten Verzeichnis und in diesem Verzeichnis befinden sich Dateien.

Das Obige funktionierte immer in Python 2, aber Python 3 traf manchmal einen ImportError oder ModuleNotFoundError (letzteres ist neu in Python 3.6 und eine Unterklasse von ImportError), sodass die folgende Optimierung für mich sowohl in Python 2 als auch in Python 3 funktioniert:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
quelle
2

Versuche dies

import components
from components import *
Vaishnavi Bala
quelle
1

Wenn jemand nach einer Problemumgehung sucht, bin ich auf eine gestoßen. Hier ist ein bisschen Kontext. Ich wollte eine der Methoden testen, die ich in einer Datei habe. Wenn ich es von innen laufen lasse

if __name__ == "__main__":

es beklagte sich immer über die relativen Importe. Ich habe versucht, die oben genannten Lösungen anzuwenden, aber es hat nicht funktioniert, da es viele verschachtelte Dateien mit jeweils mehreren Importen gab.

Folgendes habe ich getan. Ich habe gerade einen Launcher erstellt, ein externes Programm, das die erforderlichen Methoden importiert und aufruft. Obwohl keine gute Lösung, funktioniert es.

HappyWaters
quelle
0

Hier ist eine Möglichkeit, die alle verärgert, aber ziemlich gut funktioniert. In Testläufen:

ln -s ../components components

Importieren Sie dann einfach die Komponenten wie gewohnt.

SteveCalifornia
quelle
0

Dies ist sehr verwirrend, und wenn Sie IDE wie Pycharm verwenden, ist es etwas verwirrender. Was bei mir funktioniert hat: 1. Nehmen Sie Pycharm-Projekteinstellungen vor (wenn Sie Python von einem VE oder aus einem Python-Verzeichnis ausführen). 2. Die von Ihnen definierte Vorgehensweise ist nicht falsch. Manchmal funktioniert es mit der Importklasse folder1.file1

Wenn dies nicht funktioniert, verwenden Sie import folder1.file1. 3. Ihre Umgebungsvariable sollte im System korrekt erwähnt oder in Ihrem Befehlszeilenargument angegeben werden.

SANTI SANTOSH MAHAPATRA
quelle
-2

Da Ihr Code enthält if __name__ == "__main__", der nicht als Paket importiert wird, sollten Sie sys.path.append()das Problem besser lösen.

Rosefun
quelle
Ich glaube nicht, dass if __name__ == "__main__"Ihre Datei einen Unterschied zu irgendetwas im Zusammenhang mit dem Importieren macht.
user48956