Comment réparer «Tentative d'importation relative dans un non-package» même avec __init__.py

744

J'essaie de suivre PEP 328 , avec la structure de répertoires suivante:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

Dans core_test.pyJ'ai la déclaration d'importation suivante

from ..components.core import GameLoopEvents

Cependant, lorsque je cours, j'obtiens l'erreur suivante:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

En cherchant, j'ai trouvé "le chemin relatif ne fonctionnait même pas avec __init__.py " et " Importer un module à partir d'un chemin relatif " mais ils n'ont pas aidé.

Y a-t-il quelque chose qui me manque ici?

skytreader
la source
17
J'étais également très confus par les différentes façons de structurer les unittestprojets, j'ai donc écrit cet exemple de projet assez exhaustif qui couvre l'imbrication profonde des modules, les importations relatives et absolues (où le travail et non), et le référencement relatif et absolu à l'intérieur d'un package, ainsi que l'importation de classes au niveau simple, double et package. A aidé les choses claires jusqu'à pour moi!
cod3monk3y
1
Je n'ai pas pu faire fonctionner vos tests. Continuez à recevoir no module named myimports.fooquand je les exécute.
Blairg23
@ Blairg23 Je suppose que l'invocation envisagée est cddans PyImportset exécutée python -m unittest tests.test_abs, par exemple.
duozmo
7
Je suis d'accord avec Gene. Je souhaite qu'il y ait un mécanisme pour déboguer le processus d'importation qui était un peu plus utile. Dans mon cas, j'ai deux fichiers dans le même répertoire. J'essaie d'importer un fichier dans l'autre fichier. Si j'ai un fichier init .py dans ce répertoire, j'obtiens un ValueError: tentative d'importation relative dans une erreur non liée au package. Si je supprime le fichier init .py, j'obtiens une erreur sans module nommé "NAME".
user1928764
Dans mon cas, j'ai deux fichiers dans le même répertoire. J'essaie d'importer un fichier dans l'autre fichier. Si j'ai un fichier init .py dans ce répertoire, j'obtiens un ValueError: tentative d'importation relative dans une erreur non liée au package. Si je supprime le fichier init .py, j'obtiens une erreur sans module nommé "NAME". Ce qui est vraiment frustrant, c'est que j'ai eu ce travail, puis je me suis tiré une balle dans le pied en supprimant le fichier .bashrc, qui a défini le PYTHONPATH sur quelque chose, et maintenant cela ne fonctionne pas.
user1928764

Réponses:

443

Oui. Vous ne l'utilisez pas en tant que package.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
la source
51
A gotcha: Notez qu'il n'y a pas de '.py' à la fin!
mindthief
497
Je ne suis pas l'un des downvoters, mais je pense que cela pourrait utiliser un peu plus de détails, compte tenu de la popularité de cette question et réponse. Notant des choses comme à partir de quel répertoire exécuter la commande shell ci-dessus, le fait que vous ayez besoin de __init__.pytout le chemin et la __package__ruse de modification (décrite ci-dessous par BrenBarn) nécessaire pour autoriser ces importations pour les scripts exécutables (par exemple lors de l'utilisation d'un shebang et faire ./my_script.pyau shell Unix) seraient tous utiles. Toute cette question était assez difficile pour moi de comprendre ou de trouver une documentation concise et compréhensible sur.
Mark Amery
16
Remarque: vous devez être en dehors du répertoire pkgau point où vous appelez cette ligne à partir de la CLI. Ensuite, cela devrait fonctionner comme prévu. Si vous êtes à l'intérieur pkget que vous appelez python -m tests.core_test, cela ne fonctionnera pas. Au moins, ce n'était pas pour moi.
Blairg23
94
Sérieusement, pouvez-vous expliquer ce qui se passe dans votre réponse?
Pinocchio
18
@MarkAmery J'ai presque perdu la tête en essayant de comprendre comment tout cela fonctionne, les importations relatives dans un projet avec des sous-répertoires avec des fichiers py qui ont des __init__.pyfichiers mais vous continuez à recevoir l' ValueError: Attempted relative import in non-packageerreur. Je paierais vraiment très bien pour quelqu'un, quelque part, pour enfin expliquer en anglais simple comment tout cela fonctionne.
AdjunctProfessorFalcon
635

Pour développer la réponse d' Ignacio Vazquez-Abrams :

Le mécanisme d'importation Python fonctionne par rapport à celui __name__du fichier actuel. Lorsque vous exécutez un fichier directement, il n'a pas son nom habituel, mais a "__main__"plutôt son nom. Les importations relatives ne fonctionnent donc pas.

Vous pouvez, comme l'a suggéré Igancio, l'exécuter en utilisant l' -moption. Si vous avez une partie de votre package qui doit être exécutée en tant que script, vous pouvez également utiliser l' __package__attribut pour indiquer à ce fichier le nom qu'il est censé avoir dans la hiérarchie des packages.

Voir http://www.python.org/dev/peps/pep-0366/ pour plus de détails.

BrenBarn
la source
55
Il m'a fallu un certain temps pour me rendre compte que vous ne pouvez pas exécuter à python -m core_testpartir du testssous-répertoire - cela doit provenir du parent, ou vous devez ajouter le parent au chemin.
Aram Kocharyan
3
@DannyStaple: Pas exactement. Vous pouvez utiliser __package__pour vous assurer que les fichiers de script exécutables peuvent importer relativement d'autres modules à partir du même package. Il n'y a aucun moyen d'importer relativement de "l'ensemble du système". Je ne sais même pas pourquoi tu voudrais faire ça.
BrenBarn
2
Je veux dire que si le __package__symbole est défini sur "parent.child", vous pourrez alors importer "parent.other_child". Peut-être que je ne l'ai pas si bien formulé.
Danny Staple
5
@DannyStaple: Eh bien, son fonctionnement est décrit dans la documentation liée. Si vous avez un script script.pydans le package pack.subpack, puis en définissant il est __package__à pack.subpackvous laisser faire from ..module import somethingquelque chose de l'importation pack.module. Notez que, comme le dit la documentation, vous devez toujours avoir le package de niveau supérieur sur le chemin du système. C'est déjà ainsi que les choses fonctionnent pour les modules importés. La seule chose à __package__faire est de vous laisser utiliser ce comportement également pour les scripts exécutés directement.
BrenBarn
3
J'utilise __package__dans le script qui est exécuté directement mais malheureusement, j'obtiens l'erreur suivante: "Le module parent 'xxx' n'est pas chargé, ne peut pas effectuer d'importation relative"
mononoke
202

Vous pouvez utiliser import components.coredirectement si vous ajoutez le répertoire actuel à sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
ihm
la source
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))cela fonctionnera également
ajay
26
from os import sysressemble à de la triche :)
vol de moutons
3
@Piotr: Il pourrait être considéré comme meilleur car il montre légèrement plus clairement ce qui est ajouté sys.path- le parent du répertoire dans
lequel se
8
@flyingsheep: D'accord, je voudrais juste utiliser un habitué import sys, os.path as path.
martineau
10
Pour votre information, pour l' utiliser dans un ordinateur portable ipython, je me suis adapté cette réponse à: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Ensuite, une ligne droite import components.corefonctionne pour moi, en important à partir du répertoire parent du bloc-notes comme vous le souhaitez.
Racing Tadpole
195

Cela dépend de la façon dont vous souhaitez lancer votre script.

Si vous souhaitez lancer votre UnitTest à partir de la ligne de commande de manière classique, c'est:

python tests/core_test.py

Ensuite, comme dans ce cas, les «composants» et les «tests» sont des dossiers de frères et sœurs, vous pouvez importer le module relatif en utilisant l' insertion ou la méthode d' ajout du module sys.path . Quelque chose comme:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

Sinon, vous pouvez lancer votre script avec l'argument '-m' (notez que dans ce cas, nous parlons d'un paquet, et donc vous ne devez pas donner l' extension '.py' ), c'est-à-dire:

python -m pkg.tests.core_test

Dans un tel cas, vous pouvez simplement utiliser l'importation relative comme vous le faisiez:

from ..components.core import GameLoopEvents

Vous pouvez enfin mélanger les deux approches, afin que votre script fonctionne quel que soit son nom. Par exemple:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Paolo Rovelli
la source
3
que dois-je faire si j'essaie d'utiliser la pdb pour le débogage? puisque vous utilisez python -m pdb myscript.pypour lancer la session de débogage.
danny
1
@dannynjust - C'est une bonne question car vous ne pouvez pas avoir 2 modules principaux. Généralement, lors du débogage, je préfère passer manuellement dans le débogueur au premier point où je veux commencer le débogage. Vous pouvez le faire en insérant un import pdb; pdb.set_trace()dans le code (en ligne).
mgilson
3
Est-il préférable d'utiliser à la insertplace de append? Autrement dit,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
L'utilisation de l'insertion correspond mieux à la sémantique d'importation relative, où les noms de packages locaux ont priorité sur les packages installés. Surtout pour les tests, vous voulez généralement tester la version locale, pas celle installée (sauf si votre infrastructure de test installe le code sous test, auquel cas les importations relatives ne sont pas nécessaires et vous n'aurez pas ce problème).
Alex Dupuy
1
vous devez également mentionner que vous ne pouvez pas être dans le répertoire contenant core_test lorsque vous exécutez en tant que module (ce serait trop facile)
Joseph Garvin
25

Dans core_test.py, procédez comme suit:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
la source
10

Si votre cas d'utilisation est destiné à l'exécution de tests et qu'il semble que c'est le cas, vous pouvez effectuer les opérations suivantes. Au lieu d'exécuter votre script de test, python core_test.pyutilisez un framework de test tel que pytest. Ensuite, sur la ligne de commande, vous pouvez entrer

$$ py.test

Cela exécutera les tests dans votre répertoire. Cela permet de contourner le problème de l' __name__être __main__qui a été souligné par @BrenBarn. Ensuite, placez un __init__.pyfichier vide dans votre répertoire de test, cela fera du répertoire de test une partie de votre package. Ensuite, vous pourrez faire

from ..components.core import GameLoopEvents

Cependant, si vous exécutez votre script de test en tant que programme principal, les choses échoueront à nouveau. Il suffit donc d'utiliser le lanceur de test. Peut-être que cela fonctionne également avec d'autres coureurs de test tels que nosetestsmais je ne l'ai pas vérifié. J'espère que cela t'aides.

deepak
la source
9

Ma solution rapide consiste à ajouter le répertoire au chemin:

import sys
sys.path.insert(0, '../components/')
v4gil
la source
6
Votre approche ne fonctionnera pas dans tous les cas car la partie '../' est résolue à partir du répertoire à partir duquel vous exécutez votre script (core_test.py). Avec votre approche, vous êtes obligé de passer aux «tests» avant d'exécuter le scritp core_test.py.
xyman
7

Le problème vient de votre méthode de test,

tu as essayé python core_test.py

alors vous obtiendrez cette erreur ValueError: tentative d'importation relative dans un non-package

Raison: vous testez votre emballage à partir d'une source hors emballage.

testez donc votre module à partir de la source du package.

s'il s'agit de la structure de votre projet,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

ou de l'extérieur pkg /

python -m pkg.tests.core_test

unique .si vous souhaitez importer à partir du dossier dans le même répertoire. pour chaque pas en arrière, ajoutez un de plus.

hi/
  hello.py
how.py

dans how.py

from .hi import hello

incase si vous voulez importer comment de hello.py

from .. import how
Mohideen bin Mohammed
la source
1
Mieux que la réponse acceptée
GabrielBB
Dans l'exemple from .. import how, comment importez-vous une classe / méthode spécifique à partir du fichier 'how'. quand je fais l'équivalent, from ..how import fooje reçois "tentative d'importation relative au-delà du package de niveau supérieur"
James Hulse
3

Vieux fil. J'ai découvert que l'ajout d'un __all__= ['submodule', ...]au fichier __init__.py , puis l'utilisation de from <CURRENT_MODULE> import *dans la cible fonctionne correctement .

Laurent
la source
3

Vous pouvez utiliser from pkg.components.core import GameLoopEvents, par exemple, j'utilise pycharm, l'image ci-dessous est la structure de mon projet, je viens d'importer à partir du package racine, puis cela fonctionne:

entrez la description de l'image ici

Jayhello
la source
3
Cela n'a pas fonctionné pour moi. Avez-vous dû définir le chemin dans votre configuration?
Mohammad Mahjoub
3

Comme l'a dit Paolo , nous avons 2 méthodes d'invocation:

1) python -m tests.core_test
2) python tests/core_test.py

Une différence entre eux est la chaîne sys.path [0]. Puisque l'interprète recherchera sys.path lors de l'importation , nous pouvons faire avec tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

Et plus après cela, nous pouvons exécuter core_test.py avec d'autres méthodes:

cd tests
python core_test.py
python -m core_test
...

Remarque, py36 testé uniquement.

zhengcao
la source
3

Cette approche a fonctionné pour moi et est moins encombrée que certaines solutions:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

Le répertoire parent est dans mon PYTHONPATH, et il y a des __init__.pyfichiers dans le répertoire parent et ce répertoire.

Ce qui précède a toujours fonctionné en python 2, mais python 3 a parfois frappé une ImportError ou ModuleNotFoundError (ce dernier est nouveau en python 3.6 et une sous-classe d'ImportError), donc le réglage suivant fonctionne pour moi dans les deux python 2 et 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
la source
2

Essaye ça

import components
from components import *
Vaishnavi Bala
la source
1

Si quelqu'un cherche une solution de contournement, je suis tombé sur un. Voici un peu de contexte. Je voulais tester l'une des méthodes que j'ai dans un fichier. Quand je l'exécute de l'intérieur

if __name__ == "__main__":

il se plaignait toujours des importations relatives. J'ai essayé d'appliquer les solutions ci-dessus, mais j'ai échoué, car il y avait de nombreux fichiers imbriqués, chacun avec plusieurs importations.

Voici ce que j'ai fait. Je viens de créer un lanceur, un programme externe qui importerait les méthodes nécessaires et les appellerait. Bien que ce ne soit pas une excellente solution, cela fonctionne.

HappyWaters
la source
0

Voici une façon qui va énerver tout le monde mais qui fonctionne plutôt bien. Dans les tests exécutés:

ln -s ../components components

Il vous suffit ensuite d'importer des composants comme vous le feriez normalement.

SteveCalifornie
la source
0

C'est très déroutant, et si vous utilisez IDE comme pycharm, c'est un peu plus déroutant. Ce qui a fonctionné pour moi: 1. Définissez les paramètres du projet pycharm (si vous exécutez python à partir d'un VE ou d'un répertoire python) 2. Il n'y a pas de mal à la façon dont vous avez défini. parfois, il fonctionne avec la classe d'importation de folder1.file1

si cela ne fonctionne pas, utilisez import folder1.file1 3. Votre variable d'environnement doit être correctement mentionnée dans le système ou fournissez-la dans votre argument de ligne de commande.

SANTI SANTOSH MAHAPATRA
la source
-2

Parce que votre code contient if __name__ == "__main__", qui n'est pas importé en tant que package, vous feriez mieux de l'utiliser sys.path.append()pour résoudre le problème.

rosefun
la source
Je ne pense pas que le fait d'avoir if __name__ == "__main__"dans votre fichier fasse une différence pour tout ce qui concerne l'importation.
user48956