web-dev-qa-db-fra.com

au-delà de l'erreur de package de niveau supérieur lors de l'importation relative

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?

141
shelper

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.

119
Multihunter
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Essayez ceci . Travaillé pour moi.

66
jenish Sakhiya

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.

32
User

from package.A import foo

Je pense que c'est plus clair que

import sys
sys.path.append("..")
7
Joe Zhow

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())
4
dlamblin

Si quelqu'un a encore du mal à se remettre des bonnes réponses déjà fournies, pensez à vérifier ceci:

https://www.daveoncode.com/2017/03/07/how-to-solve-python-modulenotfound-no-module-named-import-error/

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.

1
Mierpo

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.

1
Jason DeMorrow

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
0
pelos