web-dev-qa-db-de.com

Relative Importe in Python 3

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?

544

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.

424
Aya

Erläuterung

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.

Lösung 1: Führen Sie Ihr Skript mit -m aus

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

Lösung 2: Stellen Sie __package__ manuell ein

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,

  1. Fügen Sie das übergeordnete Verzeichnis desN-ten-Vorgängers des aktuellen Moduls zu sys.path hinzu.

  2. Entfernen Sie das Verzeichnis der aktuellen Datei aus sys.path

  3. Importieren Sie das übergeordnete Modul des aktuellen Moduls unter Verwendung seines vollqualifizierten Namens

  4. Setze __package__ auf den vollqualifizierten Namen aus2

  5. 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

Lösung 3: Verwenden Sie absolute Importe und setuptools

Die Schritte sind -

  1. Ersetzen Sie explizite relative Importe durch äquivalente absolute Importe

  2. 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.

Lösung Nr. 4: Verwenden Sie absolute Importe und einen Boilerplate-Code

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:

  1. Fügen Sie das übergeordnete Verzeichnis vonpackagezu sys.pathhinzu, 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
    
  2. 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.

200
vaultah

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.

52
am5

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()
33
goffer

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:

  1. Führen Sie Ihren Code aus und geben Sie den Pfad explizit wie folgt ein: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. Um zu vermeiden, dass 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()
)
4
LaCroixed

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.

3
fralau

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

Lösung

Ich würde python3 im Ordner project_demo / ausführen und dann eine

from some_package import project_configs
2
Árthur

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
1
Salt999