Ich habe versucht, Fragen zu Geschwisterimporten und sogar zur Paketdokumentation durchzulesen , aber ich habe noch keine Antwort gefunden.
Mit folgender Struktur:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
Wie können die Skripte in den Verzeichnissen examples
und tests
aus dem api
Modul importiert
und über die Befehlszeile ausgeführt werden?
Außerdem möchte ich den hässlichen sys.path.insert
Hack für jede Datei vermeiden . Sicherlich kann das in Python gemacht werden, oder?
python
packages
python-import
siblings
Zachwill
quelle
quelle
sys.path
Hacks zu überspringen und die einzige tatsächliche Lösung zu lesen , die bisher veröffentlicht wurde (nach 7 Jahren!).Antworten:
Sieben Jahre später
Seit ich die Antwort unten geschrieben habe, ist das Ändern
sys.path
immer noch ein schneller und schmutziger Trick, der für private Skripte gut funktioniert, aber es wurden einige Verbesserungen vorgenommensetup.cfg
die Metadaten zu speichern).-m
Flag zu verwenden und als Paket auszuführen funktioniert ebenfalls (wird jedoch etwas umständlich, wenn Sie Ihr Arbeitsverzeichnis in ein installierbares Paket konvertieren möchten).sys.path
für Sie um die HacksEs kommt also wirklich darauf an, was Sie tun möchten. In Ihrem Fall ist die Installation durch, da es anscheinend Ihr Ziel ist, irgendwann ein richtiges Paket zu erstellen,
pip -e
wahrscheinlich die beste Wahl, auch wenn es noch nicht perfekt ist.Alte Antwort
Wie bereits an anderer Stelle erwähnt, ist die schreckliche Wahrheit, dass Sie hässliche Hacks durchführen müssen, um Importe von Geschwistermodulen oder Elternpaketen von einem
__main__
Modul zu ermöglichen. Das Problem wird in PEP 366 beschrieben . PEP 3122 hat versucht, Importe rationaler zu handhaben, aber Guido hat dies abgelehnt( hier )
Allerdings verwende ich dieses Muster regelmäßig mit
Hier
path[0]
ist der übergeordnete Ordner Ihres laufenden Skripts unddir(path[0])
Ihr Ordner der obersten Ebene.Ich war zwar immer noch nicht in der Lage, relative Importe zu verwenden, aber es erlaubt absolute Importe von der obersten Ebene (im
api
übergeordneten Ordner Ihres Beispiels ).quelle
-m
Formular ausführen oder wenn Sie das Paket installieren (pip und virtualenv machen es einfach)__package__ = "examples"
für mich ohne zu funktionieren . Warum benutzt du es? 2. In welcher Situation ist,__name__ == "__main__"
aber__package__
nichtNone
?__packages__
hilft, wenn Sie einen absoluten Pfad möchten, z. B.examples.api
um iirc zu arbeiten (aber es ist lange her, seit ich das das letzte Mal getan habe), und zu überprüfen, ob das Paket nicht None ist, war meistens ausfallsicher für seltsame Situationen und Zukunftssicherheit.Müde von sys.path Hacks?
Es gibt viele
sys.path.append
-hacks, aber ich habe einen alternativen Weg gefunden, um das Problem in der Hand zu lösen.Zusammenfassung
packaged_stuff
)setup.py
in dem Sie setuptools.setup () verwenden .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Konfiguration
Der Ausgangspunkt ist die von Ihnen bereitgestellte Dateistruktur, die in einen Ordner mit dem Namen eingeschlossen ist
myproject
.Ich werde den
.
Stammordner aufrufen und in meinem Beispiel befindet er sich unterC:\tmp\test_imports\
.api.py.
Verwenden wir als Testfall die folgende ./api/api.py
test_one.py
Versuchen Sie, test_one auszuführen:
Auch der Versuch, relative Importe durchzuführen, funktioniert nicht:
Die Verwendung
from ..api.api import function_from_api
würde dazu führenSchritte
Der Inhalt für
setup.py
wäre *Wenn Sie mit virtuellen Umgebungen vertraut sind, aktivieren Sie eine und fahren Sie mit dem nächsten Schritt fort. Die Verwendung von virtuellen Umgebungen sind nicht unbedingt erforderlich, aber sie werden wirklich Sie auf lange Sicht helfen (wenn Sie mehr als 1 Projekt laufenden haben ..). Die grundlegendsten Schritte sind (im Stammordner ausführen)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) oder./venv/Scripts/activate
(Win)Um mehr darüber zu erfahren, googeln Sie einfach "python virtual env tutorial" oder ähnliches. Sie benötigen wahrscheinlich nie andere Befehle als das Erstellen, Aktivieren und Deaktivieren.
Nachdem Sie eine virtuelle Umgebung erstellt und aktiviert haben, sollte Ihre Konsole den Namen der virtuellen Umgebung in Klammern angeben
und dein Ordnerbaum sollte so aussehen **
Installieren Sie Ihr Top-Level-Paket
myproject
mitpip
. Der Trick besteht darin,-e
bei der Installation das Flag zu verwenden . Auf diese Weise wird es in einem bearbeitbaren Zustand installiert, und alle an den .py-Dateien vorgenommenen Änderungen werden automatisch in das installierte Paket aufgenommen.Führen Sie im Stammverzeichnis aus
pip install -e .
(Beachten Sie den Punkt, er steht für "aktuelles Verzeichnis")Sie können auch sehen, dass es mithilfe von installiert wird
pip freeze
myproject.
Ihren Importen hinzuBeachten Sie, dass Sie
myproject.
nur Importe hinzufügen müssen , die sonst nicht funktionieren würden. Importe, die ohnesetup.py
&pip install
funktionierten, funktionieren weiterhin einwandfrei. Siehe ein Beispiel unten.Testen Sie die Lösung
Testen wir nun die Lösung mit
api.py
den obentest_one.py
definierten und unten definierten.test_one.py
Test ausführen
* Weitere ausführliche Beispiele für setup.py finden Sie in den setuptools-Dokumenten .
** In Wirklichkeit können Sie Ihre virtuelle Umgebung überall auf Ihrer Festplatte platzieren.
quelle
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myproject
Irgendeine Idee, wie man das löst?ModuleNotFoundError
? Ich habe 'myproject' gemäß diesen Schritten in einer virtuellen Umgebung installiert. Wenn ich eine interpretierte Sitzung betrete und ausführe,import myproject
erhalte ichModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
zeigt , dass es da ist, ist das Verzeichnis korrekt ist , und sowohl die Verison vonpip
undpython
sind korrekt verifiziert werden.pip list
zeigt Pakete, währendpip freeze
zeigt seltsame Namen, wenn mit flag -eHier ist eine weitere Alternative, die ich oben in die Python-Dateien im
tests
Ordner einfüge:quelle
..
hier relativ zu dem Verzeichnis steht, aus dem Sie ausführen - nicht zu dem Verzeichnis, das diese Test- / Beispieldatei enthält. Ich führe aus dem Projektverzeichnis aus und brauchte./
stattdessen. Hoffe das hilft jemand anderem.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerSie brauchen und sollten nicht hacken, es
sys.path
sei denn, es ist notwendig und in diesem Fall nicht. Verwenden:Aus dem Projektverzeichnis ausführen :
python -m tests.test_one
.Sie sollten sich wahrscheinlich nach
tests
innen bewegen (wenn es sich um API-Unittests handelt)api
und ausführenpython -m api.test
, um alle Tests auszuführen (vorausgesetzt, es gibt sie__main__.py
) oder stattdessenpython -m api.test.test_one
auszuführentest_one
.Sie können auch
__init__.py
ausexamples
(es ist kein Python-Paket) entfernen und die Beispiele in einer virtuellen Umgebung ausführen, in der sieapi
installiert ist, z. B.pip install -e .
in einer virtuellen Umgebung, die ein Inplace-api
Paket installiert , wenn Sie über das richtige verfügensetup.py
.quelle
python -m api.test.test_one
von überall aus ausgeführt werden können, wenn die virtuelle Umgebung aktiviert ist. Wenn Sie PyCharm nicht für die Ausführung Ihrer Tests konfigurieren können, versuchen Sie, eine neue Frage zum Stapelüberlauf zu stellen (wenn Sie zu diesem Thema keine vorhandene Frage finden).Ich habe noch nicht das Verständnis der Pythonologie, das erforderlich ist, um die beabsichtigte Art des Code-Austauschs zwischen nicht verwandten Projekten ohne einen Geschwister- / relativen Import-Hack zu erkennen. Bis zu diesem Tag ist dies meine Lösung. Für
examples
odertests
zum Importieren von Sachen..\api
würde es so aussehen:quelle
Für den Import von Geschwisterpaketen können Sie entweder die Methode insert oder append des Moduls [sys.path] [2] verwenden :
Dies funktioniert, wenn Sie Ihre Skripte wie folgt starten:
Auf der anderen Seite können Sie auch den relativen Import verwenden:
In diesem Fall müssen Sie Ihr Skript mit dem Argument '-m' starten (beachten Sie, dass Sie in diesem Fall die Erweiterung '.py' nicht angeben dürfen ):
Natürlich können Sie die beiden Ansätze mischen, damit Ihr Skript funktioniert, egal wie es heißt:
quelle
__file__
global ist, daher musste ich Folgendes verwenden:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Aber es funktioniert jetzt in jedem VerzeichnisTLDR
Diese Methode erfordert keine Setuptools, Pfad-Hacks, zusätzlichen Befehlszeilenargumente oder die Angabe der obersten Ebene des Pakets in jeder einzelnen Datei Ihres Projekts.
Erstellen Sie einfach ein Skript im übergeordneten Verzeichnis, in dem Sie sich befinden,
__main__
und führen Sie alles von dort aus aus. Zur weiteren Erklärung lesen Sie weiter.Erläuterung
Dies kann erreicht werden, ohne einen neuen Pfad zusammen zu hacken, zusätzliche Befehlszeilenargumente zu erstellen oder jedem Ihrer Programme Code hinzuzufügen, um seine Geschwister zu erkennen.
Der Grund, warum dies fehlschlägt, wie ich glaube, bereits erwähnt wurde, ist, dass die aufgerufenen Programme ihre
__name__
Einstellung als haben__main__
. In diesem Fall akzeptiert das aufgerufene Skript, dass es sich auf der obersten Ebene des Pakets befindet, und weigert sich, Skripte in Geschwisterverzeichnissen zu erkennen.Alles unter der obersten Ebene des Verzeichnisses erkennt jedoch noch ALLES ANDERE unter der obersten Ebene. Dies bedeutet , das nur , was Sie zu tun haben , Dateien zu erhalten in Geschwister Verzeichnissen , sie zu erkennen / nutzen ist , sie von einem Skript in ihrem übergeordneten Verzeichnis zu nennen.
Proof of Concept In einem Verzeichnis mit folgender Struktur:
Main.py
enthält den folgenden Code:sib1 / call.py enthält:
und sib2 / Callsib.py enthält:
Wenn Sie dieses Beispiel reproduzieren, werden Sie feststellen, dass beim Anruf
Main.py
"Get Called" gedruckt wird, wie in definiertsib2/callsib.py
, obwohl ersib2/callsib.py
angerufen wurdesib1/call.py
. Wenn man jedoch direkt aufruftsib1/call.py
(nachdem man entsprechende Änderungen an den Importen vorgenommen hat), wird eine Ausnahme ausgelöst. Obwohl es funktioniert hat, wenn es vom Skript in seinem übergeordneten Verzeichnis aufgerufen wurde, funktioniert es nicht, wenn es glaubt, auf der obersten Ebene des Pakets zu sein.quelle
Ich habe ein Beispielprojekt erstellt, um zu demonstrieren, wie ich damit umgegangen bin. Dies ist in der Tat ein weiterer sys.path-Hack, wie oben angegeben. Beispiel für den Import von Python-Geschwistern , das sich auf Folgendes stützt:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Dies scheint ziemlich effektiv zu sein, solange Ihr Arbeitsverzeichnis im Stammverzeichnis des Python-Projekts verbleibt. Wenn jemand dies in einer realen Produktionsumgebung einsetzt, wäre es schön zu hören, ob es auch dort funktioniert.
quelle
Sie müssen nachsehen, wie die Importanweisungen im zugehörigen Code geschrieben sind. Wenn
examples/example_one.py
die folgende Importanweisung verwendet wird:... dann erwartet es, dass sich das Stammverzeichnis des Projekts im Systempfad befindet.
Der einfachste Weg, dies ohne Hacks zu unterstützen (wie Sie sagen), besteht darin, die Beispiele wie folgt aus dem Verzeichnis der obersten Ebene auszuführen:
quelle
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. Ich bekomme auch das gleiche mitimport api.api
.Nur für den Fall, dass jemand, der Pydev in Eclipse verwendet, hier landet: Sie können den übergeordneten Pfad des Geschwisters (und damit den übergeordneten Pfad des aufrufenden Moduls) als externen Bibliotheksordner hinzufügen, indem Sie Projekt-> Eigenschaften verwenden und externe Bibliotheken im linken Menü Pydev-PYTHONPATH festlegen . Dann können Sie von Ihrem Geschwister importieren, z
from sibling import some_class
.quelle
Erstens sollten Sie vermeiden, Dateien mit demselben Namen wie das Modul selbst zu haben. Es kann andere Importe brechen.
Wenn Sie eine Datei importieren, überprüft der Interpreter zuerst das aktuelle Verzeichnis und durchsucht dann globale Verzeichnisse.
Drinnen
examples
odertests
Sie können anrufen:quelle
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
dem Verzeichnis der obersten Ebene eine Datei hinzufügen . Andernfalls kann Python es nicht als Modul behandeln__name__
ist__main__
stattpackage.module
, Python nicht ihre Mutter Paket sehen kann, so.
Punkte zu nichts.