Il semble qu'il y ait déjà pas mal de questions sur l'importation relative dans Python 3, mais après en avoir parcouru plusieurs, je n'ai toujours pas trouvé de réponse à mon problème. alors voici la question.
J'ai un paquet montré ci-dessous
package/
__init__.py
A/
__init__.py
foo.py
test_A/
__init__.py
test.py
et j'ai une seule ligne dans test.py:
from ..A import foo
maintenant, je suis dans le dossier de package
, et je lance
python -m test_A.test
J'ai reçu un message
"ValueError: attempted relative import beyond top-level package"
mais si je suis dans le dossier parent de package
, par exemple, je lance:
cd ..
python -m package.test_A.test
tout va bien.
Maintenant, ma question est la suivante: Lorsque je me trouve dans le dossier package
et que je lance le module à l'intérieur du sous-package test_A sous la forme test_A.test
, selon ce que j'ai compris, ..A
ne monte que d'un niveau dans le dossier package
, pourquoi il affiche un message disant beyond top-level package
. Quelle est exactement la raison qui cause ce message d'erreur?
Cette question soulève le même problème dans cette question avec une réponse plus cohérente: Importations de colis jumelés
Pourquoi ça ne marche pas? C'est parce que python n'enregistre pas d'où un paquet a été chargé. Donc, lorsque vous faites python -m test_A.test
, il supprime fondamentalement la connaissance que test_A.test
est réellement stocké dans package
(c.-à-d. Que package
n'est pas considéré comme un paquet). Tenter de tenter from ..A import foo
tente d’accéder à des informations qui n’en ont plus (par exemple, les répertoires frères d’un emplacement chargé). C'est conceptuellement similaire à autoriser from ..os import path
dans un fichier dans math
. Ce serait mauvais parce que vous voulez que les paquets soient distincts. S'ils ont besoin d'utiliser quelque chose d'un autre paquet, ils doivent les référencer globalement avec from os import path
et laisser Python déterminer où ils se trouvent avec $PATH
et $PYTHONPATH
.
Lorsque vous utilisez python -m package.test_A.test
, l'utilisation de from ..A import foo
résout parfaitement, car elle garde une trace de ce qui est dans package
et vous accédez simplement à un répertoire enfant d'un emplacement chargé.
Pourquoi python ne considère-t-il pas le répertoire de travail actuel comme un paquet?NO CLUE, mais ça alors serait utile.
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Essayez ceci . Travaillé pour moi.
Supposition:
Si vous vous trouvez dans le répertoire package
, A
et test_A
sont des packages distincts.
Conclusion:
Les importations ..A
ne sont autorisées que dans un package.
Notes complémentaires:
Rendre les importations relatives uniquement disponibles dans les packages est utile si vous souhaitez forcer la possibilité de placer les packages sur n’importe quel chemin situé sur sys.path
.
MODIFIER:
Suis-je le seul à penser que c'est insensé!? Pourquoi dans le monde le répertoire de travail actuel n'est-il pas considéré comme un paquet? - Multihunter
Le répertoire de travail actuel est généralement situé dans sys.path. Ainsi, tous les fichiers sont importables. C'est un comportement depuis Python 2 quand les paquets n'existaient pas encore. Rendre le répertoire en cours d’exécution un paquet permettrait l’importation de modules sous les noms "import .A" et "import A", qui seraient alors deux modules différents. C'est peut-être une incohérence à considérer.
from package.A import foo
Je pense que c'est plus clair que
import sys
sys.path.append("..")
Comme le suggère la réponse la plus populaire, c'est parce que votre PYTHONPATH
ou sys.path
inclut .
mais pas votre chemin d'accès à votre paquet. Et l'importation relative est relative à votre répertoire de travail actuel, pas au fichier où l'importation a lieu; bizarrement.
Vous pouvez résoudre ce problème en modifiant d'abord votre importation relative en absolu, puis en commençant par:
PYTHONPATH=/path/to/package python -m test_A.test
OU forcer le chemin python lorsqu'il est appelé de cette façon, parce que:
Avec python -m test_A.test
, vous exécutez test_A/test.py
avec __== '__main__'
et __file__ == '/absolute/path/to/test_A/test.py'
Cela signifie que dans test.py
, vous pouvez utiliser votre import
absolu semi-protégé dans la condition de cas principale et également effectuer une manipulation ponctuelle du chemin Python:
from os import path
…
def main():
…
if __== '__main__':
import sys
sys.path.append(path.join(path.dirname(__file__), '..'))
from A import foo
exit(main())
Si quelqu'un a encore du mal à se remettre des bonnes réponses déjà fournies, pensez à vérifier ceci:
Citation essentielle du site ci-dessus:
"La même chose peut être spécifiée par programme de cette façon:
système d'importation
sys.path.append ('..')
Bien entendu, le code ci-dessus doit être écrit avant l'autre importationdéclaration.
Il est assez évident que cela doit être ainsi, penser après coup. J'essayais d'utiliser sys.path.append ('..') dans mes tests, mais j'ai rencontré le problème signalé par OP. En ajoutant la définition import et sys.path avant mes autres importations, j'ai pu résoudre le problème.
Aucune de ces solutions ne fonctionnait pour moi dans 3.6, avec une structure de dossier comme:
package1/
subpackage1/
module1.py
package2/
subpackage2/
module2.py
Mon but était d'importer de module1 dans module2. Ce qui a finalement fonctionné pour moi a été curieusement:
import sys
sys.path.append(".")
Notez le point unique par opposition aux solutions à deux points mentionnées jusqu'à présent.
Edit: Ce qui suit m'a aidé à clarifier cela:
import os
print (os.getcwd())
Dans mon cas, le répertoire de travail était (de manière inattendue) la racine du projet.
si vous avez un __init__.py
dans un dossier supérieur, vous pouvez initialiser l'importation en tant que import file/path as alias
dans ce fichier d'initialisation. Ensuite, vous pouvez l’utiliser sur des scripts plus bas comme:
import alias