Importations relatives en Python 3

je veux importer une fonction à partir d'un autre fichier du même répertoire.

parfois ça marche pour moi avec from .mymodule import myfunction mais parfois j'ai un:

SystemError: Parent module '' not loaded, cannot perform relative import

parfois ça marche avec from mymodule import myfunction , mais parfois j'ai aussi un:

SystemError: Parent module '' not loaded, cannot perform relative import

Je ne comprends pas la logique ici, et je n'ai pas pu trouver d'explication. Cela semble complètement aléatoire.

est-ce que quelqu'un pourrait m'expliquer la logique derrière tout cela?

438
demandé sur martineau 2013-06-07 14:26:50

8 réponses

malheureusement, ce module doit être à l'intérieur du paquet, et elle aussi doit être exécutable comme un script, parfois. Une idée de comment je pourrais réaliser cela?

c'est assez courant d'avoir une disposition comme celle-ci...

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py

...avec un mymodule.py comme ça...

#!/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 __name__ == '__main__':
    _test()

...un myothermodule.py comme ça...

#!/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 __name__ == '__main__':
    _test()

...et un main.py comme ceci...

#!/usr/bin/env python3

from mypackage.myothermodule import add

def main():
    print(add('1', '1'))

if __name__ == '__main__':
    main()

...ce qui fonctionne très bien lorsque vous exécutez main.py ou mypackage/mymodule.py , mais échoue avec mypackage/myothermodule.py , en raison de l'importation relative...

from .mymodule import as_int

la façon dont tu es censé la diriger est...

python3 -m mypackage.myothermodule

...mais il est un peu verbeux, et ne se mélange pas bien avec une ligne de shebang comme #!/usr/bin/env python3 .

la solution la plus simple pour ce cas, en supposant le nom mymodule est unique à l'échelle mondiale, serait d'éviter d'utiliser les importations relatives, et juste l'utilisation...

from mymodule import as_int

...bien que, si ce n'est pas unique, ou si votre structure de paquet est plus complexe , vous devrez inclure le répertoire contenant votre répertoire de paquet dans PYTHONPATH , et le faire comme ceci...

from mypackage.mymodule import as_int

...ou si vous voulez qu'il fonctionne "hors de la boîte", vous pouvez frob le PYTHONPATH en code d'abord avec ceci...

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

c'est un peu une douleur, mais il y a un indice sur pourquoi dans un email écrit par un certain Guido van Rossum...

je suis -1 sur ce point et sur d'autres twiddlings de la __main__ machinerie. Le seul cas d'utilisation semble être l'exécution de scripts qui arrive vivre dans le répertoire d'un module, que j'ai toujours considéré comme antipattern. Pour me faire changer d'avis, il faudrait me convaincre que ce n'est pas le cas.

si exécuter des scripts à l'intérieur d'un paquet est un antipattern ou non est subjectif, mais personnellement je trouve qu'il est vraiment utile dans un paquet que j'ai qui contient des widgets wxPython personnalisés, donc je peux exécuter le script pour n'importe lequel des fichiers source pour afficher un wx.Frame contenant seulement ce widget à des fins de test.

352
répondu Aya 2013-06-07 13:46:06

explication

de PEP 328

importations relatives utilisez l'attribut __nom__ _ d'un module pour déterminer que la position du module dans la hiérarchie du paquet. Si le nom du module ne ne contient aucune information sur le paquet (par exemple, il est défini à " _ _ main__") puis les importations relatives sont résolues comme si le module était un niveau supérieur module , quel que soit l'emplacement du module effectivement situé sur le fichier système.

à un moment donné PEP 338 en conflit avec PEP 328 :

... les importations relatives se fondent sur _ _ nom _ _ _ pour déterminer le courant la position du module dans la hiérarchie du paquet. Dans un module principal, le la valeur de __name__ est toujours '__principaux__' , de sorte importations relatives explicites échoueront toujours (car ils ne fonctionnent que pour un module à l'intérieur d'un paquet)

et pour répondre à la question, PEP 366 introduit la variable de haut niveau __package__ :

en ajoutant un nouvel attribut de niveau de module, ce PEP permet relatif importe pour fonctionner automatiquement si le module est exécuté en utilisant le - m commutateur. Une petite quantité de boilerplate dans le module lui-même permettra les importations relatives à travailler lorsque le fichier est exécuté par nom. [...] Quand il [l'attribut] est présent, par rapport importations sera basé sur cet attribut plutôt que le module __nom__ attribut. [...] Lorsque le module principal est spécifié par son nom de fichier, l'attribut ___ paquet_ _ sera défini à None . [...] lorsque l'importation système rencontre une importation relative explicite dans un module sans__ package _ _ set (ou avec lui défini à None), il sera calculer et stocker la valeur correcte ( __nom__.rpartition('.") [0] pour les modules normaux et _ _ nom_ _ _ pour les modules d'initialisation du paquet)

(l'emphase est mienne)

si __name__ est '__main__' , __name__.rpartition('.')[0] renvoie une chaîne vide. Ce c'est pourquoi il y a une chaîne vide littérale dans la description de l'erreur:

SystemError: Parent module '' not loaded, cannot perform relative import

La partie pertinente de la Disponible PyImport_ImportModuleLevelObject fonction :

if (PyDict_GetItem(interp->modules, package) == NULL) {
    PyErr_Format(PyExc_SystemError,
            "Parent module %R not loaded, cannot perform relative "
            "import", package);
    goto error;
}

CPython soulève cette exception si elle n'a pas pu trouver package (le nom du paquet) dans interp->modules (accessible sous sys.modules ). Depuis sys.modules est " un dictionnaire qui mappe les noms de modules à modules qui ont déjà été chargés" , il est maintenant clair que le module parent doit être explicitement absolu-importé avant d'effectuer l'importation relative .

Note: le patch du numéro 18018 a ajouté un autre if bloc , qui sera exécuté avant le code ci-dessus:

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) {
    ...
*/

si package (comme ci-dessus) est une chaîne vide, le message d'erreur sera

ImportError: attempted relative import with no known parent package

cependant, vous ne le verrez qu'en Python 3.6 ou plus récent.

Solution #1: Exécutez votre script en utilisant -m

considère un répertoire (qui est un Python paquet ):

.
├── package
│   ├── __init__.py
│   ├── module.py
│   └── standalone.py

tous les fichiers de package commencez par les mêmes 2 lignes de code:

from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())

j'inclus ces deux lignes seulement pour rendre l'ordre des opérations évident. Nous pouvons les ignorer complètement, car ils n'affectent pas l'exécution.

__init__.py et module.py ne contient que ces deux lignes (c'est-à-dire qu'elles sont effectivement vides).

standalone.py tente en outre d'importer module.py via relative import:

from . import module  # explicit relative import

nous sommes bien conscients que /path/to/python/interpreter package/standalone.py échouera. Cependant, nous pouvons exécuter le module avec le -m option de ligne de commande qui va "rechercher sys.path pour le module nommé et exécuter son contenu comme le __main__ module" :

"
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 fait toutes les choses d'importation pour vous et définit automatiquement __package__ , mais vous pouvez le faire vous-même dans le

Solution n ° 2: Régler__ paquet _ _ manuellement

veuillez le considérer comme une preuve de concept plutôt que comme une solution réelle. Il n'est pas bien adapté pour une utilisation en code réel.

PEP 366 a une solution de contournement ce problème, cependant, il est incomplet, parce que le réglage __package__ seul n'est pas suffisant. Vous devrez importer au moins N précédant les paquets dans la hiérarchie du module, où n est le nombre de répertoires parent (relatif au répertoire du script) qui seront recherchés pour le module importé.

ainsi,

  1. ajouter le répertoire parent du Nth prédécesseur du module actuel à sys.path

  2. Supprimer l'annuaire du fichier courant de sys.path

  3. importer le module parent du module courant en utilisant son nom complet

  4. ensemble __package__ au nom complet de 2

  5. effectuer l'importation relative

je vais emprunter des fichiers de la Solution #1 et ajouter quelques sous-paquets:

package
├── __init__.py
├── module.py
└── subpackage
    ├── __init__.py
    └── subsubpackage
        ├── __init__.py
        └── standalone.py

cette fois standalone.py importera module.py du colis colis utilisant l'importation relative suivante

from ... import module  # N = 3

nous aurons besoin de précéder cette ligne avec le code du boilerplate, pour le faire fonctionner.

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

il nous permet d'exécuter standalone.py par nom de fichier:

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

une solution plus générale enveloppée dans une fonction peut être trouvée ici . Exemple d'utilisation:

if __name__ == '__main__' and __package__ is None:
    import_parents(level=3) # N = 3

from ... import module
from ...module.submodule import thing

Solution #3: Utiliser les importations absolues et setuptools

Les étapes sont -

  1. remplacer les importations relatives explicites par les importations absolues équivalentes

  2. "
  3. Installer package pour qu'il soit importables

par exemple, la structure du répertoire peut être la suivante

.
├── project
│   ├── package
│   │   ├── __init__.py
│   │   ├── module.py
│   │   └── standalone.py
│   └── setup.py

setup.py est

from setuptools import setup, find_packages
setup(
    name = 'your_package_name',
    packages = find_packages(),
)

le reste des fichiers a été emprunté à la Solution #1 .

L'Installation de

vous permettra d'importer le paquet quel que soit votre répertoire de travail (en supposant qu'il n'y aura pas de problèmes de nommage).

nous pouvons modifier standalone.py pour utiliser cet avantage (étape 1):

from package import module  # absolute import

changez votre répertoire de travail en project et lancez /path/to/python/interpreter setup.py install --user ( --user installe le paquet dans votre site-répertoire des paquets ) (étape 2):

vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user

vérifions qu'il est maintenant possible d'exécuter standalone.py comme un script:

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'>

Note : si vous décidez de suivre cette voie, il est préférable d'utiliser environnements virtuels pour installer les paquets dans l'isolement.

Solution #4: Utiliser les importations absolues et un code de bobine

franchement, l'installation n'est pas nécessaire - vous pourriez ajouter du code boilerplate à votre script pour faire fonctionner absolute imports.

je vais emprunter les fichiers de Solution n ° 1 et changer standalone.py :

  1. ajouter le parent répertoire de paquet à sys.path avant tentative d'importer quoi que ce soit de paquet en utilisant les importations absolues:

    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. remplacer l'importation relative par l'importation absolue:

    from package import module  # absolute import
    

standalone.py fonctionne sans problème:

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'>

je pense que je devrais vous avertir: essayez de ne pas faire cela, surtout si votre projet a une structure complexe.


Comme une note de côté, PEP 8 recommande l'utilisation de l'absolu des importations, mais précise que, dans certains scénarios explicite relative des importations sont acceptables:

les importations absolues sont recommandées, car elles sont généralement plus lisibles et ont tendance à être plus sage (ou au moins donner une meilleure erreur message.) [...] Toutefois, les importations relatives explicites sont acceptables. alternative aux importations absolues, en particulier lorsqu'il s'agit de complexes la disposition des paquets où l'utilisation d'importations absolues serait inutilement verbeux.

156
répondu vaultah 2017-12-31 11:40:35

j'ai rencontré ce numéro. Une solution de contournement est l'importation via un bloc si / else comme suit:

#!/usr/bin/env python3
#myothermodule

if __name__ == '__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 __name__ == '__main__':
    _test()
28
répondu goffer 2018-01-13 19:22:45

mettez ça dans votre paquet. init__.py fichier :

# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

si votre paquet est comme ceci:

├── project
│   ├── package
│   │   ├── __init__.py
│   │   ├── module1.py
│   │   └── module2.py
│   └── setup.py

utilisez maintenant des importations régulières dans votre emballage, comme:

# in module2.py
from module1 import class1

cela fonctionne en python 2 et 3.

11
répondu am5 2018-05-16 12:38:26

si les deux paquets sont dans votre chemin d'importation (sys.path) , et le module / classe que vous voulez est dans example/example.py, puis accéder à la classe sans importation relative essayer:

from example.example import fkt
1
répondu Salt999 2017-03-09 22:18:30

pour éviter ce problème, j'ai conçu une solution avec le paquet reconditionnement , qui a fonctionné pour moi pendant un certain temps. Il ajoute le répertoire supérieur au chemin de lib:

import repackage
repackage.up()
from mypackage.mymodule import myfunction

reconditionnement peut faire des importations relatives qui fonctionnent dans un large éventail de cas, en utilisant une stratégie intelligente (inspection de la pile d'appels).

1
répondu fralau 2017-12-06 09:42:19

avec un peu de chance, cela aura de la valeur pour quelqu'un là - bas-j'ai passé en revue une demi-douzaine de poteaux stackoverflow essayant de comprendre les importations relatives similaires à ce qui est affiché ci-dessus. J'ai tout mis en place comme suggéré mais je continuais à taper ModuleNotFoundError: No module named 'my_module_name'

comme je ne faisais que développer localement et jouer, je n'avais pas créé/lancé un fichier setup.py . Je n'avais pas non plus apparemment mis mon PYTHONPATH .

j'ai réalisé que quand j'ai couru mon code comme je l'avais été lorsque les tests étaient dans le même répertoire que le module, Je ne pouvais pas trouver mon module:

$ 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'

cependant, quand j'ai spécifié explicitement le chemin, les choses ont commencé à fonctionner:

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

donc, dans le cas où quelqu'un a essayé quelques suggestions, croit que leur code est structuré correctement et se trouve toujours dans une situation similaire à moi-même essayer l'un des suivants si vous n'exportez pas le répertoire courant vers votre PYTHONPATH:

  1. exécutez votre code et incluez explicitement le chemin comme ceci: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. pour éviter d'appeler PYTHONPATH=. , créez un fichier setup.py avec le contenu suivant et lancez python setup.py development pour ajouter des paquets au chemin:
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)
1
répondu LaCroixed 2018-04-17 19:40:32

pour moi, j'avais besoin d'exécuter python3 à partir du répertoire principal pour le faire fonctionner. Par exemple, si le projet a la structure suivante:


project_demo

/ - main.py

/ - - some_package

| - - - - __ init __.py

/ - - - - project_configs.py

/ - - essai

/ - - - - test_project_configs.py


Solution: je lancerais python3 à l'intérieur de project_demo et puis à partir de" some_package import project_configs .

0
répondu Árthur 2018-09-11 18:49:29