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?
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.
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,
-
ajouter le répertoire parent du Nth prédécesseur du module actuel à
sys.path
-
Supprimer l'annuaire du fichier courant de
sys.path
-
importer le module parent du module courant en utilisant son nom complet
-
ensemble
__package__
au nom complet de 2 -
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 -
-
remplacer les importations relatives explicites par les importations absolues équivalentes
"
-
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
où 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 devous 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 :
-
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
-
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.
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()
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.
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
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).
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:
- exécutez votre code et incluez explicitement le chemin comme ceci:
$ PYTHONPATH=. python3 test/my_module/module_test.py
- pour éviter d'appeler
PYTHONPATH=.
, créez un fichiersetup.py
avec le contenu suivant et lancezpython setup.py development
pour ajouter des paquets au chemin:
# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
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 .