jenseits des Paketfehlers der obersten Ebene beim relativen Import

316

Es scheint, dass es hier bereits einige Fragen zum relativen Import in Python 3 gibt, aber nachdem ich viele davon durchgesehen habe, habe ich immer noch keine Antwort auf mein Problem gefunden. Also hier ist die Frage.

Ich habe ein Paket unten gezeigt

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

und ich habe eine einzelne Zeile in test.py:

from ..A import foo

Jetzt bin ich im Ordner von packageund laufe

python -m test_A.test

Ich habe eine Nachricht erhalten

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

aber wenn ich mich im übergeordneten Ordner von packagez. B. befinde , starte ich:

cd ..
python -m package.test_A.test

alles ist gut.

Jetzt ist meine Frage: Wenn ich mich im Ordner von packagebefinde und das Modul innerhalb des Unterpakets test_A ausführe test_A.test, ..Asteigt nach meinem Verständnis nur eine Ebene nach oben, die sich noch im packageOrdner befindet, weshalb es eine Meldung gibt beyond top-level package. Was genau ist der Grund für diese Fehlermeldung?

Shelper
quelle
49
Dieser Beitrag hat meinen Fehler "
Beyond
4
Ich habe hier einen Gedanken. Wenn also test_A.test als Modul ausgeführt wird, geht '..' über test_A hinaus, was bereits die höchste Ebene des Imports test_A.test ist. Ich denke, die Paketebene ist nicht die Verzeichnisebene, sondern wie viele Ebenen, die Sie das Paket importieren.
Shelper
2
Ich verspreche Ihnen, dass Sie alles über den relativen Import verstehen werden, nachdem Sie diese Antwort auf stackoverflow.com/a/14132912/8682868 gesehen haben .
pzjzeason
Ausführliche Erläuterungen zu diesem Problem finden Sie unter ValueError: Versuch eines relativen Imports über das Paket der obersten Ebene hinaus .
Napuzba
Gibt es eine Möglichkeit, relative Importe zu vermeiden? Wie sieht PyDev in Eclipse alle Pakete in <PydevProject> / src?
Mushu909

Antworten:

172

EDIT: Es gibt bessere / kohärentere Antworten auf diese Frage in anderen Fragen:


Warum funktioniert es nicht? Dies liegt daran, dass Python nicht aufzeichnet, woher ein Paket geladen wurde. Wenn Sie dies tun python -m test_A.test, wird im Grunde genommen nur das Wissen verworfen, in dem test_A.testtatsächlich gespeichert ist package(dh es packagewird nicht als Paket betrachtet). Beim Versuch from ..A import foowird versucht, auf Informationen zuzugreifen, die nicht mehr vorhanden sind (dh Geschwisterverzeichnisse eines geladenen Speicherorts). Es ist konzeptionell ähnlich wie das Einlassen from ..os import patheiner Datei in math. Dies wäre schlecht, da die Pakete unterschiedlich sein sollen. Wenn sie etwas aus einem anderen Paket verwenden müssen, sollten sie global mit auf sie verweisen from os import pathund Python herausfinden lassen, wo sich das mit $PATHund befindet $PYTHONPATH.

Wenn Sie verwenden python -m package.test_A.test, wird die Verwendung von from ..A import fooAuflösungen einwandfrei ausgeführt, da der Überblick über die Inhalte besteht packageund Sie nur auf ein untergeordnetes Verzeichnis eines geladenen Speicherorts zugreifen.

Warum betrachtet Python das aktuelle Arbeitsverzeichnis nicht als Paket? Keine Ahnung , aber meine Güte , es wäre nützlich.

Multihunter
quelle
2
Ich habe meine Antwort bearbeitet, um auf eine bessere Antwort auf eine Frage zu verweisen, die dasselbe bedeutet. Es gibt nur Problemumgehungen. Das einzige, was ich tatsächlich gesehen habe, ist, was das OP getan hat, nämlich das -mFlag zu verwenden und aus dem obigen Verzeichnis auszuführen.
Multihunter
1
Es sollte beachtet werden, dass diese Antwort aus dem von Multihunter gegebenen Link nicht den sys.pathHack beinhaltet, sondern die Verwendung von Setuptools , was meiner Meinung nach viel interessanter ist.
Angelo Cardellicchio
157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Versuche dies. Hat für mich gearbeitet.

jenish Sakhiya
quelle
10
Ähm ... wie würde diese Arbeit funktionieren? Jede einzelne Testdatei hätte dies?
George Mauer
Das Problem hier ist, ob zB A/bar.pyexistiert und in foo.pydir from .bar import X.
user1834164
9
Ich musste das .. aus "from ..A import ..." entfernen, nachdem ich den sys.path.append ("..")
hinzugefügt hatte
2
Wenn das Skript von außerhalb des vorhandenen Verzeichnisses ausgeführt wird, funktioniert dies nicht. Stattdessen müssen Sie diese Antwort optimieren, um den absoluten Pfad des Skripts anzugeben .
Manavalan Gajapathy
Dies ist die beste, am wenigsten komplizierte Option
Alex R
43

Annahme:
Wenn Sie sich im packageVerzeichnis befinden Aund test_Aseparate Pakete sind.

Fazit:
..AImporte sind nur innerhalb eines Pakets erlaubt.

Weitere Hinweise: Es
ist hilfreich, die relativen Importe nur innerhalb von Paketen verfügbar zu machen, wenn Sie erzwingen möchten, dass Pakete auf einem beliebigen Pfad platziert werden können sys.path.

BEARBEITEN:

Bin ich der einzige, der das für verrückt hält? Warum in aller Welt wird das aktuelle Arbeitsverzeichnis nicht als Paket betrachtet? - Multihunter

Das aktuelle Arbeitsverzeichnis befindet sich normalerweise in sys.path. Alle Dateien dort können also importiert werden. Dies ist das Verhalten seit Python 2, als noch keine Pakete vorhanden waren. Wenn Sie das laufende Verzeichnis zu einem Paket machen, können Sie Module als "import .A" und als "import A" importieren. Dies sind dann zwei verschiedene Module. Vielleicht ist dies eine zu berücksichtigende Inkonsistenz.

Benutzer
quelle
86
Bin ich der einzige, der das für verrückt hält? Warum in aller Welt wird das laufende Verzeichnis nicht als Paket betrachtet?
Multihunter
13
Das ist nicht nur verrückt, das ist auch nicht hilfreich. Wie führen Sie dann Tests durch? Klar, was das OP gefragt hat und warum ich sicher bin, dass auch viele Leute hier sind.
George Mauer
Das laufende Verzeichnis befindet sich normalerweise in sys.path. Alle Dateien dort können also importiert werden. Dies ist das Verhalten seit Python 2, als noch keine Pakete vorhanden waren. - bearbeitete Antwort.
Benutzer
Ich folge der Inkonsistenz nicht. Das Verhalten von python -m package.test_A.testscheint zu tun, was gewünscht wird, und mein Argument ist, dass dies die Standardeinstellung sein sollte. Können Sie mir ein Beispiel für diese Inkonsistenz geben?
Multihunter
Ich denke tatsächlich, gibt es eine Feature-Anfrage dafür? Das ist in der Tat verrückt. C / C ++ - Stil #includewäre sehr nützlich!
Nicholas Humphrey
29

Keine dieser Lösungen funktionierte in Version 3.6 für mich mit einer Ordnerstruktur wie:

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

Mein Ziel war es, von Modul1 in Modul2 zu importieren. Was für mich schließlich funktionierte, war seltsamerweise:

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

Beachten Sie den einzelnen Punkt im Gegensatz zu den bisher erwähnten Zwei-Punkt-Lösungen.


Bearbeiten: Folgendes hat mir geholfen, dies zu klären:

import os
print (os.getcwd())

In meinem Fall war das Arbeitsverzeichnis (unerwartet) das Stammverzeichnis des Projekts.

Jason DeMorrow
quelle
2
Es funktioniert lokal, aber nicht auf einer aws ec2-Instanz. Ist das sinnvoll?
Thebeancounter
Das hat auch bei mir funktioniert - in meinem Fall war das Arbeitsverzeichnis ebenfalls das Projektstammverzeichnis. Ich habe eine Verknüpfung von einem Programmiereditor (TextMate) verwendet
JeremyDouglass
@thebeancounter Same! Funktioniert lokal auf meinem Mac, aber nicht auf ec2. Dann wurde mir klar, dass ich den Befehl in einem Unterverzeichnis auf ec2 ausführte und ihn lokal im Stammverzeichnis ausführte. Sobald ich es von root auf ec2 ausgeführt habe, hat es funktioniert.
Logan Yang
Dies funktionierte auch für mich sehr geschätzt. Von dieser sys-Methode kann ich jetzt einfach das Paket aufrufen, ohne ".." zu benötigen
RamWill
sys.path.append(".")funktioniert, weil Sie es im übergeordneten Verzeichnis aufrufen. Beachten Sie, dass es .immer das Verzeichnis darstellt, in dem Sie den Python-Befehl
ausführen
13

from package.A import foo

Ich denke es ist klarer als

import sys
sys.path.append("..")
Joe Zhow
quelle
4
es ist sicher besser lesbar, muss aber noch sys.path.append(".."). getestet auf Python 3.6
MFA
Gleich wie ältere Antworten
Nrofis
12

Da die populärste Antwort schon sagt, im Grunde sein , weil Ihre PYTHONPATHoder sys.pathschließt .aber nicht Ihr Weg zu Ihrem Paket. Der relative Import bezieht sich auf Ihr aktuelles Arbeitsverzeichnis und nicht auf die Datei, in die der Import erfolgt. seltsamerweise.

Sie können dies beheben, indem Sie zuerst Ihren relativen Import in absolut ändern und dann entweder mit:

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

ODER erzwingen Sie den Python-Pfad, wenn Sie auf diese Weise aufgerufen werden, weil:

Mit führen python -m test_A.testSie test_A/test.pymit __name__ == '__main__'und aus__file__ == '/absolute/path/to/test_A/test.py'

Das bedeutet, dass test.pySie importin der Hauptfallbedingung Ihr absolutes halbgeschütztes verwenden und auch eine einmalige Python-Pfadmanipulation durchführen können:

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
quelle
8

Bearbeiten: 2020-05-08: Anscheinend wird die von mir zitierte Website nicht mehr von der Person kontrolliert, die den Rat geschrieben hat, daher entferne ich den Link zur Website. Danke, dass du mich über baxx informiert hast.


Wenn jemand nach den großartigen Antworten immer noch Probleme hat, habe ich auf einer Website Ratschläge gefunden, die nicht mehr verfügbar sind.

Wesentliches Zitat von der Website, die ich erwähnt habe:

"Dasselbe kann programmgesteuert folgendermaßen angegeben werden:

sys importieren

sys.path.append ('..')

Natürlich muss der obige Code vor der anderen Importanweisung geschrieben werden.

Es ist ziemlich offensichtlich, dass es so sein muss, wenn man nachträglich darüber nachdenkt. Ich habe versucht, sys.path.append ('..') in meinen Tests zu verwenden, bin jedoch auf das von OP gepostete Problem gestoßen. Durch Hinzufügen der Definition von import und sys.path vor meinen anderen Importen konnte ich das Problem lösen.

Mierpo
quelle
Der Link, den Sie gepostet haben, ist tot.
Baxx
Danke für die Information. Es scheint, dass der Domainname nicht mehr von derselben Person kontrolliert wird. Ich habe den Link entfernt.
Mierpo
5

Wenn Sie eine __init__.pyin einem oberen Ordner haben, können Sie den Import wie import file/path as aliasin dieser Init-Datei initialisieren . Dann können Sie es für niedrigere Skripte verwenden als:

import alias
Pelos
quelle
0

Meiner bescheidenen Meinung nach verstehe ich diese Frage folgendermaßen:

[FALL 1] Wenn Sie einen absoluten Import wie starten

python -m test_A.test

oder

import test_A.test

oder

from test_A import test

Sie setzen den Import-Anker tatsächlich so test_A, dass er ein Paket der obersten Ebene ist test_A. Wenn wir also test.py do haben from ..A import xxx, entkommen Sie dem Anker, und Python lässt dies nicht zu.

[FALL 2] Wenn Sie dies tun

python -m package.test_A.test

oder

from package.test_A import test

Ihr Anker wird package, so zu package/test_A/test.pytun , from ..A import xxxden Anker (noch im Innern nicht entkommen packageOrdner) und Python glücklich akzeptiert dies.

Zusamenfassend:

  • Absoluter Import ändert den aktuellen Anker (= definiert das Paket der obersten Ebene neu);
  • Der relative Import ändert den Anker nicht, sondern beschränkt sich darauf.

Darüber hinaus können wir verwenden Voll qualifizierte Modulnamen (FQMN) Um dieses Problem zu untersuchen.

Überprüfen Sie jeweils FQMN:

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

Für CASE2 führt a zu from .. import xxxeinem neuen Modul mit FQMN = package.xxx, was akzeptabel ist.

Während für CASE1 der ..von innen from .. import xxxaus dem Startknoten (Anker) von springt, test_Aist dies von Python NICHT zulässig.

Jimm Chen
quelle
2
Dies ist viel komplizierter als es sein muss. Soviel zum Zen of Python.
AtilioA
0

Nicht sicher in Python 2.x, aber in Python 3.6, vorausgesetzt, Sie versuchen, die gesamte Suite auszuführen, müssen Sie nur verwenden -t

-t, --top-level-Verzeichnisverzeichnis Top-Level-Verzeichnis des Projekts (standardmäßig Startverzeichnis)

Also auf einer Struktur wie

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

Man könnte zum Beispiel verwenden:

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

Und immer noch die my_module.my_classohne große Dramen importieren .

Andre de Miranda
quelle