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

317

Il semble qu'il y ait déjà pas mal de questions ici sur l'importation relative dans python 3, mais après avoir parcouru bon nombre d'entre elles, je n'ai toujours pas trouvé la réponse à mon problème. voici donc la question.

J'ai un package 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 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: quand je suis dans le dossier de package, et que j'exécute le module à l'intérieur du sous-package test_A car test_A.test, selon ma compréhension, ..Amonte d'un seul niveau, qui est toujours dans le packagedossier, pourquoi il donne un message disant beyond top-level package. Quelle est exactement la raison qui provoque ce message d'erreur?

abri
la source
49
ce message n'a pas expliqué mon erreur "au-delà du package de niveau supérieur"
abri
4
J'ai une pensée ici, donc lorsque vous exécutez test_A.test en tant que module, '..' va au-dessus de test_A, qui est déjà le plus haut niveau de l'importation test_A.test, je pense que le niveau du package n'est pas le niveau du répertoire, mais combien niveaux que vous importez le package.
abri
2
Je vous promets que vous comprendrez tout sur l'importation relative après avoir regardé cette réponse stackoverflow.com/a/14132912/8682868 .
pzjzeason
voir ValueError: tentative d'importation relative au-delà du package de niveau supérieur pour des explications détaillées sur ce problème.
napuzba
Existe-t-il un moyen d'éviter de faire des importations relatives? Comme la façon dont PyDev dans Eclipse voit tous les packages dans <PydevProject> / src?
Mushu909

Réponses:

173

EDIT: Il existe des réponses meilleures / plus cohérentes à cette question dans d'autres questions:


Pourquoi ça ne marche pas? C'est parce que python n'enregistre pas d'où un paquet a été chargé. Ainsi, lorsque vous le faites python -m test_A.test, il supprime simplement les connaissances qui test_A.testsont réellement stockées package(c'est package-à- dire qu'il n'est pas considéré comme un package). Tenter, from ..A import fooc'est essayer d'accéder à des informations dont il n'a plus (c'est-à-dire les répertoires frères d'un emplacement chargé). C'est conceptuellement similaire à autoriser from ..os import pathun fichier dans math. Ce serait mauvais car vous voulez que les packages soient distincts. S'ils ont besoin d'utiliser quelque chose d'un autre paquet, ils doivent s'y référer globalement avec from os import pathet laisser python déterminer où cela se trouve avec $PATHet $PYTHONPATH.

Lorsque vous utilisez python -m package.test_A.test, l'utilisation de from ..A import fooresolves est très bien car il a gardé une trace de ce qui se trouve packageet vous accédez simplement à un répertoire enfant d'un emplacement chargé.

Pourquoi python ne considère-t-il pas que le répertoire de travail actuel est un package? AUCUN INDICE , mais ça serait utile.

Multihunter
la source
2
J'ai modifié ma réponse pour faire référence à une meilleure réponse à une question qui équivaut à la même chose. Il n'y a que des solutions de contournement. La seule chose que j'ai réellement vu fonctionner est ce que l'OP a fait, qui est d'utiliser l' -mindicateur et de l'exécuter à partir du répertoire ci-dessus.
Multihunter
1
Il convient de noter que cette réponse , à partir du lien donné par Multihunter, n'implique pas le sys.pathhack, mais l'utilisation de setuptools , ce qui est beaucoup plus intéressant à mon avis.
Angelo Cardellicchio
157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Essaye ça. A travaillé pour moi.

jenish Sakhiya
la source
10
Umm ... comment cela fonctionnerait-il? Chaque fichier de test unique aurait cela?
George Mauer
Le problème ici est si, par exemple, A/bar.pyexiste et en foo.pyvous from .bar import X.
user1834164
9
J'ai dû supprimer le .. de "de .. une importation ..." après avoir ajouté le sys.path.append ("..")
Jake OPJ
2
Si le script est exécuté depuis l'extérieur du répertoire, il existe, cela ne fonctionnera pas. Au lieu de cela, vous devez modifier cette réponse pour spécifier le chemin absolu dudit script .
Manavalan Gajapathy
c'est la meilleure option, la moins compliquée
Alex R
43

Hypothèse:
si vous êtes dans le packagerépertoire Aet que vous avez test_Ades packages distincts.

Conclusion: les
..Aimportations ne sont autorisées que dans un package.

Notes supplémentaires:
Rendre les importations relatives uniquement disponibles dans les packages est utile si vous souhaitez forcer le placement des packages sur n'importe quel chemin situé sur sys.path.

ÉDITER:

Suis-je le seul à penser que c'est fou!? Pourquoi dans le monde le répertoire de travail actuel n'est-il pas considéré comme un package? - Multihunter

Le répertoire de travail actuel se trouve généralement dans sys.path. Ainsi, tous les fichiers sont importables. Il s'agit d'un comportement depuis Python 2 lorsque les packages n'existaient pas encore. Faire du répertoire en cours d'exécution un package permettrait d'importer des modules comme "import .A" et comme "import A" qui seraient alors deux modules différents. C'est peut-être une incohérence à considérer.

Utilisateur
la source
86
Suis-je le seul à penser que c'est fou!? Pourquoi dans le monde le répertoire en cours d'exécution n'est-il pas considéré comme un package?
Multihunter
13
Non seulement c'est fou, c'est inutile ... alors comment exécutez-vous les tests alors? De toute évidence, la chose que le PO demandait et pourquoi je suis sûr que beaucoup de gens sont ici également.
George Mauer
Le répertoire en cours d'exécution se trouve généralement dans sys.path. Ainsi, tous les fichiers sont importables. Il s'agit d'un comportement depuis Python 2 lorsque les packages n'existaient pas encore. - réponse modifiée.
Utilisateur
Je ne suis pas incohérent. Le comportement de python -m package.test_A.testsemble faire ce qui est voulu, et mon argument est que ce devrait être la valeur par défaut. Alors, pouvez-vous me donner un exemple de cette incohérence?
Multihunter
Je pense en fait, y a-t-il une demande de fonctionnalité pour cela? C'est vraiment fou. Le style C / C ++ #includeserait très utile!
Nicholas Humphrey
29

Aucune de ces solutions ne fonctionnait pour moi en 3.6, avec une structure de dossiers comme:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Mon objectif était d'importer du module1 vers le module2. Ce qui a finalement fonctionné pour moi, c'est assez curieusement:

import sys
sys.path.append(".")

Notez le point unique par opposition aux solutions à deux points mentionnées jusqu'à présent.


Edit: Les éléments suivants ont aidé à clarifier cela pour moi:

import os
print (os.getcwd())

Dans mon cas, le répertoire de travail était (de manière inattendue) la racine du projet.

Jason DeMorrow
la source
2
cela fonctionne localement mais ne fonctionne pas sur l'instance aws ec2, cela a-t-il un sens?
thebeancounter
Cela a également fonctionné pour moi - dans mon cas, le répertoire de travail était également la racine du projet. J'utilisais un raccourci d'exécution d'un éditeur de programmation (TextMate)
JeremyDouglass
@thebeancounter Idem! Fonctionne localement sur mon mac mais ne fonctionne pas sur ec2, alors j'ai réalisé que j'exécutais la commande dans un sous-répertoire sur ec2 et l'exécutais à la racine localement. Une fois que je l'ai exécuté depuis root sur ec2, cela a fonctionné.
Logan Yang
Cela a également fonctionné pour moi très apprécié. A partir de cette méthode sys, je peux maintenant simplement appeler le paquet sans avoir besoin de ".."
RamWill
sys.path.append(".")travaillé parce que vous l'appelez dans le répertoire parent, notez qu'il .représente toujours le répertoire dans lequel vous exécutez la commande python.
KevinZhou
13

from package.A import foo

Je pense que c'est plus clair que

import sys
sys.path.append("..")
Joe Zhow
la source
4
il est plus lisible à coup sûr mais a encore besoin sys.path.append(".."). testé sur python 3.6
MFA
Identique aux réponses plus anciennes
nrofis
12

Comme le suggère la réponse la plus populaire, c'est essentiellement parce que votre PYTHONPATHou sys.pathinclut .mais pas votre chemin vers votre package. Et l'importation relative est relative à votre répertoire de travail actuel, pas au fichier où l'importation se produit; bizarrement.

Vous pouvez résoudre ce problème en modifiant d'abord votre importation relative en absolu, puis en le 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, car:

Avec python -m test_A.testvous exécutez test_A/test.pyavec __name__ == '__main__'et__file__ == '/absolute/path/to/test_A/test.py'

Cela signifie que test.pyvous pouvez utiliser votre importsemi-protégé absolu dans la condition de cas principale et également effectuer une manipulation de chemin Python unique:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())
dlamblin
la source
8

Edit: 2020-05-08: Il semble que le site Web que j'ai cité ne soit plus contrôlé par la personne qui a écrit le conseil, donc je supprime le lien vers le site. Merci de m'avoir informé de baxx.


Si quelqu'un a encore du mal après les bonnes réponses déjà fournies, j'ai trouvé des conseils sur un site Web qui n'est plus disponible.

Citation essentielle du site que j'ai mentionné:

"La même chose peut être spécifiée par programme de cette manière:

importer sys

sys.path.append ('..')

Bien sûr, le code ci-dessus doit être écrit avant l'autre instruction d' importation .

Il est assez évident que cela doit être ainsi, en y réfléchissant 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 l'importation et la définition de sys.path avant mes autres importations, j'ai pu résoudre le problème.

Mierpo
la source
le lien que vous avez publié est mort.
baxx
Merci de me le faire savoir. Il semble que le nom de domaine ne soit plus contrôlé par la même personne. J'ai supprimé le lien.
Mierpo
5

si vous en avez un __init__.pydans un dossier supérieur, vous pouvez initialiser l'importation comme import file/path as aliasdans ce fichier init. Ensuite, vous pouvez l'utiliser sur des scripts inférieurs comme:

import alias
pelos
la source
0

À mon humble avis, je comprends cette question de cette façon:

[CAS 1] Lorsque vous démarrez une importation absolue comme

python -m test_A.test

ou

import test_A.test

ou

from test_A import test

vous définissez en fait l' import-ancre pour être test_A, en d'autres termes, le package de niveau supérieur est test_A. Donc, lorsque nous avons test.py do from ..A import xxx, vous vous échappez de l'ancre, et Python ne le permet pas.

[CAS 2] Quand vous le faites

python -m package.test_A.test

ou

from package.test_A import test

votre ancre devient package, ce package/test_A/test.pyfaisant, from ..A import xxxn'échappe pas à l'ancre (toujours dans le packagedossier), et Python accepte cela avec plaisir.

En bref:

  • L'importation absolue modifie l'ancre actuelle (= redéfinit ce qu'est le package de niveau supérieur);
  • L'importation relative ne change pas l'ancre mais la limite.

En outre, nous pouvons utiliser le nom de module complet (FQMN) pour inspecter ce problème.

Vérifiez le FQMN dans chaque cas:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Ainsi, pour CASE2, un from .. import xxxentraînera un nouveau module avec FQMN = package.xxx, ce qui est acceptable.

Alors que pour CASE1, le ..de l'intérieur from .. import xxxsautera du nœud de départ (ancre) de test_A, et cela n'est PAS autorisé par Python.

Jimm Chen
la source
2
C'est beaucoup plus compliqué qu'il ne faut. Voilà pour Zen of Python.
AtilioA
0

Pas sûr en python 2.x mais en python 3.6, en supposant que vous essayez d'exécuter toute la suite, il vous suffit d'utiliser -t

-t, --top-level-directory directory Répertoire de niveau supérieur du projet (par défaut le répertoire de démarrage)

Donc, sur une structure comme

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

On pourrait par exemple utiliser:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

Et toujours importer les my_module.my_classsans drames majeurs.

Andre de Miranda
la source