Erläuterung
Aus PEP 328
Relative Importe verwenden das Attribut __name__ eines Moduls, um die Position dieses Moduls in der Pakethierarchie zu bestimmen. Wenn der Name des Moduls keine Paketinformationen enthält (z. B. auf '__main__' gesetzt), werden
relative Importe so aufgelöst, als wäre das Modul ein Modul der obersten Ebene , unabhängig davon, wo sich das Modul tatsächlich im Dateisystem befindet.
Irgendwann kollidierte PEP 338 mit PEP 328 :
... relative Importe basieren auf __name__ , um die Position des aktuellen 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 in einem Paket funktionieren).
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 können mit diesem PEP relative Importe automatisch ausgeführt werden, wenn das Modul mit der Option -m
ausgeführt wird. Mit einer kleinen Menge Boilerplate im Modul selbst können die relativen Importe ausgeführt werden, wenn die Datei namentlich ausgeführt wird. [...] Wenn [das Attribut] vorhanden ist, basieren relative Importe auf diesem Attribut und nicht auf dem Attribut Modul __name__ . [...] Wenn das Hauptmodul durch seinen Dateinamen angegeben wird, wird das Attribut __package__ auf None gesetzt . [...] Wenn das Importsystem einen expliziten relativen Import in einem Modul ohne __package__ (oder mit None) feststellt, berechnet und speichert es den korrekten Wert (__name __. rpartition ('.') [0] für normale Module und __name__ für Paketinitialisierungsmodule )
(Hervorhebung von mir)
Wenn dies der Fall __name__
ist '__main__'
, wird eine __name__.rpartition('.')[0]
leere Zeichenfolge zurückgegeben. Aus diesem Grund enthält die Fehlerbeschreibung ein leeres Zeichenfolgenliteral:
SystemError: Parent module '' not loaded, cannot perform relative import
Der relevante Teil der CPython- PyImport_ImportModuleLevelObject
Funktion :
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
es sich um ein "Wörterbuch handelt, das Modulnamen bereits geladenen Modulen zuordnet" , ist jetzt klar, dass das übergeordnete Modul explizit absolut importiert werden muss, bevor ein relativer Import durchgeführt wird .
Hinweis: Der Patch aus der Ausgabe 18018 hat einen weiteren if
Block hinzugefügt, der vor dem obigen Codeausgeführt wird:
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
Dies wird jedoch nur in Python 3.6 oder höher angezeigt.
Lösung 1: Führen Sie Ihr Skript mit -m aus
Betrachten Sie ein Verzeichnis (das ein Python- Paket ist ):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Alle Dateien im Paket beginnen mit denselben 2 Codezeilen:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Ich füge diese beiden Zeilen nur hinzu , um die Reihenfolge der Operationen zu verdeutlichen. Wir können sie vollständig ignorieren, da sie die Ausführung nicht beeinflussen.
__init__.py und module.py enthalten nur diese beiden Zeilen (dh sie sind effektiv leer).
standalone.py versucht zusätzlich, module.py über den relativen Import zu importieren:
from . import module # explicit relative import
Wir sind uns bewusst, dass dies /path/to/python/interpreter package/standalone.py
fehlschlagen wird. Wir können das Modul jedoch mit der -m
Befehlszeilenoption ausführen , die "nach sys.path
dem benannten Modul sucht und dessen Inhalt als __main__
Modul ausführt " :
vaultah@base:~$ 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 ganzen Import für dich und setzt automatisch __package__
, aber das kannst du selbst in der
Lösung 2: Stellen Sie __package__ manuell ein
Bitte behandeln Sie es eher als Proof of Concept als als tatsächliche Lösung. Es ist nicht gut für die Verwendung in realem Code geeignet.
PEP 366 bietet eine Problemumgehung für dieses Problem, ist jedoch unvollständig, da die Einstellung __package__
allein nicht ausreicht. Sie müssen mindestens N vorhergehende Pakete in die Modulhierarchie importieren , wobei N die Anzahl der übergeordneten Verzeichnisse (relativ zum Verzeichnis des Skripts) ist, die nach dem zu importierenden Modul durchsucht werden.
Somit,
Fügen Sie das übergeordnete Verzeichnis des N-ten Vorgängers des aktuellen Moduls hinzusys.path
Entfernen Sie das Verzeichnis der aktuellen Datei aus sys.path
Importieren Sie das übergeordnete Modul des aktuellen Moduls unter Verwendung seines vollständig qualifizierten Namens
Stellen Sie __package__
den vollständig qualifizierten Namen von 2 ein
Führen Sie den relativen Import durch
Ich werde Dateien aus der Lösung Nr. 1 ausleihen und weitere Unterpakete hinzufügen:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Dieses Mal standalone.py importiert module.py aus dem Paket - Paket mit dem folgenden relativen Import
from ... import module # N = 3
Wir müssen dieser Zeile den Boilerplate-Code voranstellen, damit er funktioniert.
import sys
from pathlib import Path
if __name__ == '__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
Es ermöglicht uns, standalone.py nach Dateiname auszuführen :
vaultah@base:~$ 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, die in eine Funktion eingeschlossen ist, finden Sie hier . Anwendungsbeispiel:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Lösung 3: Verwenden Sie absolute Importe und Setuptools
Die Schritte sind -
Ersetzen Sie explizite relative Importe durch äquivalente absolute Importe
Installieren package
, um es importierbar zu machen
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 aus der Lösung Nr. 1 ausgeliehen .
Durch die Installation können Sie das Paket unabhängig von Ihrem Arbeitsverzeichnis importieren (vorausgesetzt, es treten keine Namensprobleme 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 es aus /path/to/python/interpreter setup.py install --user
( --user
installiert das Paket in Ihrem Site-Packages-Verzeichnis ) (Schritt 2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Lassen Sie uns überprüfen, ob es jetzt möglich ist, standalone.py als Skript auszuführen :
vaultah@base:~/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'>
Hinweis : Wenn Sie sich für diesen Weg entscheiden, sollten Sie virtuelle Umgebungen verwenden , um Pakete isoliert zu installieren.
Lösung 4: Verwenden Sie absolute Importe und etwas Boilerplate-Code
Ehrlich gesagt ist die Installation nicht erforderlich - Sie können Ihrem Skript Boilerplate-Code hinzufügen, damit absolute Importe funktionieren.
Ich werde Dateien von Lösung 1 ausleihen und standalone.py ändern :
Fügen Sie das übergeordnete Verzeichnis des Pakets hinzu , sys.path
bevor Sie versuchen, mithilfe absoluter Importe etwas aus dem Paket 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:
vaultah@base:~$ 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 sich tendenziell besser verhalten (oder zumindest bessere Fehlermeldungen liefern). [...] Explizite relative Importe sind jedoch eine akzeptable Alternative zu absoluten Importen, insbesondere bei komplexen Paketlayouts, bei denen die Verwendung absoluter Importe unnötig ausführlich wäre.