J'essaie de suivre PEP 328 , avec la structure de répertoire suivante:
pkg/
__init__.py
components/
core.py
__init__.py
tests/
core_test.py
__init__.py
Dans core_test.py
j'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 autour de moi, j'ai trouvé " le chemin relatif ne fonctionnait pas, même 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?
Oui. Vous ne l'utilisez pas comme un paquet.
python -m pkg.tests.core_test
Pour élaborer sur Ignacio Vazquez-Abrams réponse:
Le mécanisme d'importation Python fonctionne par rapport au __name__
du fichier actuel. Lorsque vous exécutez un fichier directement, il ne porte pas son nom habituel, mais le nom "__main__"
. Les importations relatives ne fonctionnent donc pas.
Comme suggéré par Igancio, vous pouvez l'exécuter en utilisant l'option -m
. Si une partie de votre package doit être exécutée en tant que script, vous pouvez également utiliser l'attribut __package__
pour indiquer au fichier le nom qu'il est supposé avoir dans la hiérarchie des packages.
Voir http://www.python.org/dev/peps/pep-0366/ pour plus de détails.
Vous pouvez utiliser import components.core
directement si vous ajoutez le répertoire en cours à sys.path
:
if __== '__main__' and __package__ is None:
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
Cela dépend de la façon dont vous voulez lancer votre script.
Si vous voulez lancez votre UnitTest à partir de la ligne de commande de manière classique, c'est-à-dire:
python tests/core_test.py
Ensuite, comme dans ce cas, 'composants' et 'tests' sont des dossiers frères, vous pouvez importer le module relatif en utilisant la méthode insert ou append du fichier sys.path module . 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, vous ne devez donc pas donner l'extension '.py'), C'est:
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, peu importe comment il s'appelle . Par exemple:
if __== '__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
Dans core_test.py, procédez comme suit:
import sys
sys.path.append('../components')
from core import GameLoopEvents
Si votre cas d'utilisation est d'exécuter des tests et que cela semble être le cas, vous pouvez procéder comme suit. Au lieu d'exécuter votre script de test en tant que python core_test.py
, utilisez un framework de test tel que pytest
. Ensuite, sur la ligne de commande, vous pouvez entrer
$$ py.test
Cela va exécuter les tests dans votre répertoire. Ceci résout le problème de __name__
étant __main__
qui a été souligné par @BrenBarn. Ensuite, placez un fichier __init__.py
vide dans votre répertoire de test, cela fera du répertoire de test une partie de votre paquet. 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 encore une fois. Alors utilisez simplement le testeur. Peut-être que cela fonctionne aussi avec d'autres testeurs tels que nosetests
mais je ne l'ai pas vérifié. J'espère que cela t'aides.
Ma solution rapide consiste à ajouter le répertoire au chemin:
import sys
sys.path.insert(0, '../components/')
Vieux fil. J'ai découvert que l'ajout d'un __all__= ['submodule', ...]
au fichier __init__.py , puis l'utilisation du from <CURRENT_MODULE> import *
dans la cible fonctionnent correctement.
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 fichiers __init__.py
dans le répertoire parent et dans ce répertoire.
Essaye ça
import components
from components import *
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 le lance de l'intérieur
if __== "__main__":
il s'est toujours plaint des importations relatives. J'ai essayé d'appliquer les solutions ci-dessus, mais sans succès, car il y avait beaucoup de fichiers imbriqués, chacun avec plusieurs importations.
Voici ce que j'ai fait. Je viens de créer un programme de lancement, un programme externe permettant d'importer les méthodes nécessaires et de les appeler. Bien que ce ne soit pas une bonne solution, cela fonctionne.
Comme Paolo a dit, 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étation cherchera sys.path lors de l’import , nous pouvons faire avec tests/core_test.py
:
if __== '__main__':
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from components import core
<other stuff>
Et après, 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.