Ich möchte eine Funktion aus einer anderen Datei im selben Verzeichnis importieren.
Manchmal funktioniert es bei mir mit from .mymodule import myfunction
, aber manchmal bekomme ich ein:
SystemError: Parent module '' not loaded, cannot perform relative import
Manchmal funktioniert es mit from mymodule import myfunction
, aber manchmal bekomme ich auch ein:
SystemError: Parent module '' not loaded, cannot perform relative import
Ich verstehe die Logik hier nicht und konnte keine Erklärung finden. Das sieht völlig zufällig aus.
Könnte mir jemand erklären, was die Logik dahinter ist?
leider muss dieses Modul im Paket enthalten sein und manchmal muss es auch als Skript ausgeführt werden können. Irgendeine Idee, wie ich das erreichen könnte?
Es ist durchaus üblich, ein Layout wie dieses zu haben ...
_main.py
mypackage/
__init__.py
mymodule.py
myothermodule.py
_
... mit einem _mymodule.py
_ wie diesem ...
_#!/usr/bin/env python3
# Exported function
def as_int(a):
return int(a)
# Test function for module
def _test():
assert as_int('1') == 1
if __== '__main__':
_test()
_
... ein _myothermodule.py
_ wie folgt ...
_#!/usr/bin/env python3
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __== '__main__':
_test()
_
... und ein _main.py
_ wie dieses ...
_#!/usr/bin/env python3
from mypackage.myothermodule import add
def main():
print(add('1', '1'))
if __== '__main__':
main()
_
... was gut funktioniert, wenn Sie _main.py
_ oder _mypackage/mymodule.py
_ ausführen, aber aufgrund des relativen Imports mit _mypackage/myothermodule.py
_ fehlschlägt ...
_from .mymodule import as_int
_
Die Art, wie Sie es ausführen sollen, ist ...
_python3 -m mypackage.myothermodule
_
... aber es ist etwas ausführlich und passt nicht gut zu einer Shebang-Linie wie _#!/usr/bin/env python3
_.
Die einfachste Lösung für diesen Fall, vorausgesetzt, der Name mymodule
ist global eindeutig, besteht darin, relative Importe zu vermeiden und einfach ...
_from mymodule import as_int
_
... obwohl, wenn es nicht eindeutig ist oder Ihre Paketstruktur komplexer ist, Sie das Verzeichnis, das Ihr Paketverzeichnis enthält, in PYTHONPATH
aufnehmen müssen und dies so tun müssen ...
_from mypackage.mymodule import as_int
_
... oder wenn Sie möchten, dass es "out of the box" funktioniert, können Sie zuerst das PYTHONPATH
im Code mit diesem ...
_import sys
import os
PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))
from mypackage.mymodule import as_int
_
Es ist eine Art Schmerz, aber es gibt einen Hinweis darauf, warum in eine E-Mail von einem bestimmten Guido van Rossum geschrieben ...
Ich bin -1 bei diesem und allen anderen vorgeschlagenen Twiddlings der _
__main__
_-Maschinerie. Der einzige Anwendungsfall scheint das Ausführen von Skripten zu sein, die sich zufällig im Verzeichnis eines Moduls befinden, das ich immer als Antipattern gesehen habe. Um mich dazu zu bringen, meine Meinung zu ändern, müsstest du mich davon überzeugen, dass es nicht so ist.
Ob das Ausführen von Skripten in einem Paket ein Antipattern ist oder nicht, ist subjektiv, aber ich persönlich finde es sehr nützlich in einem Paket, das einige benutzerdefinierte wxPython-Widgets enthält, damit ich das Skript für jede der Quelldateien ausführen kann, um ein _wx.Frame
_ enthält nur dieses Widget zu Testzwecken.
Von PEP 328
Relative Importe verwenden das Attribut __ eines Moduls, um die Position dieses Moduls in der Pakethierarchie zu bestimmen. Wenn der Name des Moduls keine Paketinformationen enthält (z. B. '__main__') , werden relative Importe so aufgelöst, als wäre das Modul ein Modul der obersten Ebene , unabhängig davon, wo Das Modul befindet sich tatsächlich im Dateisystem.
Irgendwann PEP 338 kollidierte mit PEP 328 :
... relative Importe basieren auf_ NAME _, um die aktuelle Position des Moduls in der Pakethierarchie zu bestimmen. In einem Hauptmodul ist der Wert von_ NAME _immer'__ main __', sodass explizite relative Importe immer fehlschlagen (da sie nur für ein Modul innerhalb von funktionieren) Paket)
und um das Problem zu beheben, führte PEP 366 die Variable der obersten Ebene ein __package__
:
Durch Hinzufügen eines neuen Attributs auf Modulebene ermöglicht dieses PEP, dass relative Importe automatisch ausgeführt werden, wenn das Modul mit dem Schalter- mausgeführt wird. Eine kleine Menge Boilerplate im Modul selbst ermöglicht den relativen Import, wenn die Datei namentlich ausgeführt wird. [...] Wenn [das Attribut] vorhanden ist, basieren relative Importe auf diesem Attribut und nicht auf dem Modul_ NAME _attribute. [...] Wenn das Hauptmodul durch seinen Dateinamen angegeben wird, wird das Attribut_ PACKAGE _aufNonegesetzt. [...] Wenn das Importsystem auf einen expliziten relativen Import in einem Modul ohne __package__ set (oder mit None) stößt, berechnet und speichert es den korrekten Wert ( __ name __. rpartition ('.') [0] für normale Module und_ NAME _für Paketinitialisierungsmodule)
(Hervorhebung von mir)
Wenn der __name__
'__main__'
ist, gibt __name__.rpartition('.')[0]
eine leere Zeichenfolge zurück. Aus diesem Grund enthält die Fehlerbeschreibung ein leeres Zeichenfolgenliteral:
SystemError: Parent module '' not loaded, cannot perform relative import
Der relevante Teil der CPython-Funktion PyImport_ImportModuleLevelObject
:
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
CPython löst diese Ausnahme aus, wenn es package
(den Namen des Pakets) in interp->modules
(zugänglich als sys.modules
) nicht finden konnte. Da sys.modules
"ein Wörterbuch ist, das Modulnamen auf bereits geladene Module abbildet", ist jetzt klar, dass das übergeordnete Modul explizit absolut importiert werden muss, bevor es relativ ausgeführt wird import .
Hinweis: Der Patch aus der Ausgabe 18018 hat einen weiteren if
Block hinzugefügt , was ausgeführt wird vor der Code oben:
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
Wenn package
(wie oben) eine leere Zeichenfolge ist, wird die Fehlermeldung angezeigt
ImportError: attempted relative import with no known parent package
Sie sehen dies jedoch nur in Python 3.6 oder neuer.
Betrachten Sie ein Verzeichnis (das ein Python package ist):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Alle Dateien inpackagebeginnen mit denselben 2 Codezeilen:
from pathlib import Path
print('Running' if __== '__main__' else 'Importing', Path(__file__).resolve())
Ich beziehe diese beiden Zeilennurein, um die Reihenfolge der Operationen zu verdeutlichen. Wir können sie komplett ignorieren, da sie die Ausführung nicht beeinflussen.
__init__.py und module.py enthalten nur diese beiden Zeilen (d. h. sie sind praktisch leer).
standalone.py versucht zusätzlich, module.py über relativen Import zu importieren:
from . import module # explicit relative import
Wir sind uns bewusst, dass /path/to/python/interpreter package/standalone.py
fehlschlagen wird. Wir können das Modul jedoch mit der Befehlszeilenoption -m
ausführen , mit der"nach sys.path
für das angegebene Modul gesucht wird und dessen Inhalt als __main__
-Modul ausgeführt wird":
[email protected]:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
erledigt den gesamten Import für Sie und setzt automatisch __package__
, aber das können Sie selbst in der
Bitte behandeln Sie es eher als Proof-of-Concept als als eine tatsächliche Lösung. Es ist nicht gut geeignet für die Verwendung in Code der realen Welt .
PEP 366 hat eine Problemumgehung, die jedoch unvollständig ist, da das Festlegen von __package__
allein nicht ausreicht. Sie müssen mindestensNvorangehende Pakete in die Modulhierarchie importieren, wobeiNdie Anzahl der übergeordneten Verzeichnisse (relativ) ist in das Verzeichnis des Skripts), das nach dem zu importierenden Modul durchsucht wird.
Somit,
Fügen Sie das übergeordnete Verzeichnis desN-ten-Vorgängers des aktuellen Moduls zu sys.path
hinzu.
Entfernen Sie das Verzeichnis der aktuellen Datei aus sys.path
Importieren Sie das übergeordnete Modul des aktuellen Moduls unter Verwendung seines vollqualifizierten Namens
Setze __package__
auf den vollqualifizierten Namen aus2
Führen Sie den relativen Import durch
Ich leihe mir Dateien aus derLösung 1und füge weitere Unterpakete hinzu:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Dieses Mal importiert standalone.pymodule.py aus dempackagepackage mit dem folgenden relativen Import
from ... import module # N = 3
Wir müssen der Zeile den Kesselschild-Code voranstellen, damit es funktioniert.
import sys
from pathlib import Path
if __== '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
Damit können wir standalone.py nach Dateiname ausführen:
[email protected]:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
Eine allgemeinere Lösung in einer Funktion finden Sie hier . Anwendungsbeispiel:
if __== '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Die Schritte sind -
Ersetzen Sie explizite relative Importe durch äquivalente absolute Importe
Installieren Sie package
, damit es importiert werden kann
Beispielsweise kann die Verzeichnisstruktur wie folgt sein
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
wo setup.py ist
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
Der Rest der Dateien wurde vonSolution # 1ausgeliehen.
Bei der Installation können Sie das Paket unabhängig von Ihrem Arbeitsverzeichnis importieren (vorausgesetzt, es treten keine Benennungsprobleme auf).
Wir können standalone.py ändern, um diesen Vorteil zu nutzen (Schritt 1):
from package import module # absolute import
Ändern Sie Ihr Arbeitsverzeichnis in project
und führen Sie /path/to/python/interpreter setup.py install --user
aus (--user
installiert das Paket in Ihrem Site-Paketverzeichnis ) (Schritt 2):
[email protected]:~$ cd project
[email protected]:~/project$ python3 setup.py install --user
Stellen wir sicher, dass es jetzt möglich ist, standalone.py als Skript auszuführen:
[email protected]:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.Egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.Egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.Egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.Egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.Egg/package/module.py'>
Note: Wenn Sie sich für diesen Weg entscheiden, sollten Sie virtuelle Umgebungen verwenden, um Pakete zu installieren isoliert.
Ehrlich gesagt, ist die Installation nicht erforderlich - Sie können Ihrem Skript einen Code hinzufügen, um absolute Importe zu ermöglichen.
Ich werde Dateien vonSolution # 1ausleihen und standalone.py ändern:
Fügen Sie das übergeordnete Verzeichnis vonpackagezu sys.path
hinzu, bevorversucht wird, etwas auspackagemit absoluten Importen zu importieren:
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
Ersetzen Sie den relativen Import durch den absoluten Import:
from package import module # absolute import
standalone.py läuft ohne Probleme:
[email protected]:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
Ich bin der Meinung, dass ich Sie warnen sollte: Versuchen Sie, dies nicht zu tun,insbesondere, wenn Ihr Projekt eine komplexe Struktur aufweist.
Als Randnotiz empfiehlt PEP 8 die Verwendung absoluter Importe, gibt jedoch an, dass in einigen Szenarien explizite relative Importe akzeptabel sind:
Absolute Importe werden empfohlen, da sie normalerweise besser lesbar sind und ein besseres Verhalten aufweisen (oder zumindest bessere Fehlermeldungen liefern). [...] Explizite relative Importe sind jedoch eine akzeptable Alternative zu absoluten Importen, insbesondere bei komplexen Paketlayouts, bei denen die Verwendung von absoluten Importen unnötig ausführlich wäre.
Fügen Sie dies in die __init__.py-Datei Ihres Pakets ein:
# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
Angenommen, Ihr Paket sieht folgendermaßen aus:
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ └── module2.py
│ └── setup.py
Verwenden Sie jetzt reguläre Importe in Ihrem Paket, wie:
# in module2.py
from module1 import class1
Dies funktioniert sowohl in python 2 als auch in _ 3.
Ich bin auf dieses Problem gestoßen. Eine Hack-Problemumgehung wird über einen if/else-Block wie folgt importiert:
#!/usr/bin/env python3
#myothermodule
if __== '__main__':
from mymodule import as_int
else:
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __== '__main__':
_test()
Hoffentlich ist dies für jemanden da draußen von Wert - ich habe ein halbes Dutzend Stapelüberlauf-Posts durchgesehen, um die relativen Importe herauszufinden, die den oben angegebenen ähnlich sind. Ich habe alles wie vorgeschlagen eingerichtet, aber ich habe immer noch ModuleNotFoundError: No module named 'my_module_name'
getroffen
Da ich nur lokal entwickelte und herumspielte, hatte ich keine setup.py
-Datei erstellt/ausgeführt. Ich hatte anscheinend auch nicht mein PYTHONPATH
gesetzt.
Ich stellte fest, dass ich mein Modul nicht finden konnte, als ich meinen Code so ausführte, wie ich es war, als sich die Tests im selben Verzeichnis wie das Modul befanden:
$ python3 test/my_module/module_test.py 2.4.0
Traceback (most recent call last):
File "test/my_module/module_test.py", line 6, in <module>
from my_module.module import *
ModuleNotFoundError: No module named 'my_module'
Als ich jedoch den Pfad explizit spezifizierte, begannen die Dinge zu funktionieren:
$ PYTHONPATH=. python3 test/my_module/module_test.py 2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s
OK
Falls also jemand ein paar Vorschläge ausprobiert hat, glaubt er, dass sein Code korrekt strukturiert ist und sich dennoch in einer ähnlichen Situation wie ich befindet, versuchen Sie eine der folgenden Möglichkeiten, wenn Sie das aktuelle Verzeichnis nicht in Ihren PYTHONPATH exportieren:
$ PYTHONPATH=. python3 test/my_module/module_test.py
PYTHONPATH=.
aufgerufen wird, erstellen Sie eine setup.py
-Datei mit folgendem Inhalt und führen Sie python setup.py development
aus, um dem Pfad Pakete hinzuzufügen:# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
Um dieses Problem zu umgehen, habe ich eine Lösung mit dem repackage -Paket entwickelt, die seit einiger Zeit für mich funktioniert. Es fügt das obere Verzeichnis dem lib-Pfad hinzu:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Repackage kann mithilfe einer intelligenten Strategie (Überprüfen des Aufrufstapels) relative Importe durchführen, die in einer Vielzahl von Fällen funktionieren.
Ich musste python3 aus dem Hauptprojektverzeichnis ausführen, damit es funktioniert.
Zum Beispiel, wenn das Projekt die folgende Struktur hat:
project_demo/
├── main.py
├── some_package/
│ ├── __init__.py
│ └── project_configs.py
└── test/
└── test_project_configs.py
Ich würde python3 im Ordner project_demo / ausführen und dann eine
from some_package import project_configs
wenn sich beide Pakete in Ihrem Importpfad (sys.path) befinden und das gewünschte Modul/die gewünschte Klasse in example/example.py ist, versuchen Sie, auf die Klasse ohne relativen Import zuzugreifen:
from example.example import fkt