J'ai essayé de lire des questions sur les importations de frères et même la documentation du paquet , mais je n'ai pas encore trouvé de réponse.
Avec la structure suivante:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
Comment les scripts dans les examples
et tests
répertoires importer à partir du
api
module et être exécuté à partir du commandline?
De plus, j'aimerais éviter le sys.path.insert
truc horrible pour chaque fichier. Cela peut sûrement être fait en Python, non?
python
packages
python-import
siblings
Zachwill
la source
la source
sys.path
hacks et de lire la seule solution réelle qui a été publiée jusqu'à présent (après 7 ans!).Réponses:
Sept ans après
Depuis que j'ai écrit la réponse ci-dessous, la modification
sys.path
est toujours une astuce rapide qui fonctionne bien pour les scripts privés, mais il y a eu plusieurs améliorationssetup.cfg
pour stocker les métadonnées)-m
indicateur et l'exécution en tant que package fonctionnent également (mais s'avéreront un peu gênantes si vous souhaitez convertir votre répertoire de travail en un package installable).sys.path
hacks pour vousCela dépend donc vraiment de ce que vous voulez faire. Dans votre cas, cependant, comme il semble que votre objectif soit de créer un package approprié à un moment donné, l'installation via
pip -e
est probablement votre meilleur pari, même si elle n'est pas encore parfaite.Ancienne réponse
Comme déjà indiqué ailleurs, la terrible vérité est que vous devez faire de vilains hacks pour autoriser les importations à partir de modules frères ou de packages parents à partir d'un
__main__
module. La question est détaillée dans la PEP 366 . La PEP 3122 a tenté de traiter les importations d'une manière plus rationnelle, mais Guido l'a rejetée à cause de( ici )
Cependant, j'utilise ce modèle régulièrement avec
# Ugly hack to allow absolute import from the root folder # whatever its name is. Please forgive the heresy. if __name__ == "__main__" and __package__ is None: from sys import path from os.path import dirname as dir path.append(dir(path[0])) __package__ = "examples" import api
Voici
path[0]
le dossier parent de votre script en cours d'exécution etdir(path[0])
votre dossier de niveau supérieur.Je n'ai toujours pas pu utiliser les importations relatives avec cela, mais cela permet des importations absolues depuis le niveau supérieur (dans le
api
dossier parent de votre exemple ).la source
-m
form ou si vous installez le package (pip et virtualenv facilitent les choses)__package__ = "examples"
pour moi. Pourquoi l'utilisez-vous? 2. Dans quelle situation se trouve__name__ == "__main__"
mais__package__
n'est pasNone
?__packages__
utile si vous voulez un chemin absolu tel queexamples.api
travailler iirc (mais cela fait longtemps que je n'ai pas fait cela pour la dernière fois) et vérifier que le paquet n'est pas None était principalement une solution de sécurité pour les situations étranges et la pérennité.Fatigué des hacks sys.path?
Il existe de nombreux
sys.path.append
-hacks disponibles, mais j'ai trouvé un autre moyen de résoudre le problème en cours.Sommaire
packaged_stuff
)setup.py
script où vous utilisez setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Installer
Le point de départ est la structure de fichiers que vous avez fournie, enveloppée dans un dossier appelé
myproject
.J'appellerai le
.
dossier racine, et dans mon exemple, il se trouve àC:\tmp\test_imports\
.api.py
Comme cas de test, utilisons ce qui suit ./api/api.py
def function_from_api(): return 'I am the return value from api.api!'
test_one.py
from api.api import function_from_api def test_function(): print(function_from_api()) if __name__ == '__main__': test_function()
Essayez d'exécuter test_one:
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py Traceback (most recent call last): File ".\myproject\tests\test_one.py", line 1, in <module> from api.api import function_from_api ModuleNotFoundError: No module named 'api'
Essayer également les importations relatives ne fonctionnera pas:
L'utilisation
from ..api.api import function_from_api
entraîneraitPS C:\tmp\test_imports> python .\myproject\tests\test_one.py Traceback (most recent call last): File ".\tests\test_one.py", line 1, in <module> from ..api.api import function_from_api ValueError: attempted relative import beyond top-level package
Pas
Le contenu du
setup.py
serait *from setuptools import setup, find_packages setup(name='myproject', version='1.0', packages=find_packages())
Si vous êtes familiarisé avec les environnements virtuels, activez-en un et passez à l'étape suivante. L'utilisation d'environnements virtuels n'est pas absolument nécessaire, mais ils vous aideront vraiment à long terme (lorsque vous avez plus d'un projet en cours ..). Les étapes les plus élémentaires sont (exécutées dans le dossier racine)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) ou./venv/Scripts/activate
(Win)Pour en savoir plus à ce sujet, il suffit de rechercher sur Google "tutoriel d'environnement virtuel python" ou similaire. Vous n'avez probablement jamais besoin d'autres commandes que la création, l'activation et la désactivation.
Une fois que vous avez créé et activé un environnement virtuel, votre console doit donner le nom de l'environnement virtuel entre parenthèses
et votre arborescence de dossiers devrait ressembler à ceci **
. ├── myproject │ ├── api │ │ ├── api_key.py │ │ ├── api.py │ │ └── __init__.py │ ├── examples │ │ ├── example_one.py │ │ ├── example_two.py │ │ └── __init__.py │ ├── LICENCE.md │ ├── README.md │ └── tests │ ├── __init__.py │ └── test_one.py ├── setup.py └── venv ├── Include ├── Lib ├── pyvenv.cfg └── Scripts [87 entries exceeds filelimit, not opening dir]
Installez votre package de niveau supérieur en
myproject
utilisantpip
. L'astuce consiste à utiliser le-e
drapeau lors de l'installation. De cette façon, il est installé dans un état modifiable et toutes les modifications apportées aux fichiers .py seront automatiquement incluses dans le package installé.Dans le répertoire racine, exécutez
pip install -e .
(notez le point, il signifie "répertoire courant")Vous pouvez également voir qu'il est installé en utilisant
pip freeze
(venv) PS C:\tmp\test_imports> pip install -e . Obtaining file:///C:/tmp/test_imports Installing collected packages: myproject Running setup.py develop for myproject Successfully installed myproject (venv) PS C:\tmp\test_imports> pip freeze myproject==1.0
myproject.
à vos importationsNotez que vous devrez ajouter
myproject.
uniquement dans les importations qui ne fonctionneraient pas autrement. Les importations qui ont fonctionné sans lesetup.py
&pip install
fonctionneront toujours correctement. Voir un exemple ci-dessous.Testez la solution
Maintenant, testons la solution en utilisant
api.py
définie ci-dessus ettest_one.py
définie ci-dessous.test_one.py
from myproject.api.api import function_from_api def test_function(): print(function_from_api()) if __name__ == '__main__': test_function()
exécuter le test
(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py I am the return value from api.api!
* Voir la documentation de setuptools pour des exemples plus détaillés de setup.py.
** En réalité, vous pouvez placer votre environnement virtuel n'importe où sur votre disque dur.
la source
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myproject
Une idée sur la façon de résoudre?ModuleNotFoundError
? J'ai installé «myproject» dans un virtualenv en suivant ces étapes, et lorsque j'entre dans une session interprétée et que j'exécute,import myproject
j'obtiensModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
montre qu'il est là, que le répertoire est correct et que la version depip
etpython
est vérifiée comme étant correcte.Voici une autre alternative que j'insère en haut des fichiers Python dans le
tests
dossier:# Path hack. import sys, os sys.path.insert(0, os.path.abspath('..'))
la source
..
qu'ici est relatif au répertoire à partir duquel vous exécutez --- pas le répertoire contenant ce fichier test / exemple. J'exécute à partir du répertoire du projet, et j'avais besoin à la./
place. J'espère que ceci aide quelqu'un d'autre.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerVous n'avez pas besoin et ne devriez pas pirater
sys.path
sauf si c'est nécessaire et dans ce cas, ce n'est pas le cas. Utilisation:import api.api_key # in tests, examples
Exécuter à partir du répertoire du projet:
python -m tests.test_one
.Vous devriez probablement vous déplacer
tests
(si ce sont les tests unitaires de l'API) à l'intérieurapi
et exécuterpython -m api.test
pour exécuter tous les tests (en supposant qu'il y en ait un__main__.py
) oupython -m api.test.test_one
pour exécuter à latest_one
place.Vous pouvez également supprimer
__init__.py
deexamples
(ce n'est pas un package Python) et exécuter les exemples dans un virtualenv oùapi
est installé par exemple,pip install -e .
dans un virtualenv installerait leapi
package inplace si vous avez le bonsetup.py
.la source
python -m api.test.test_one
de n'importe où lorsque le virtualenv est activé. Si vous ne pouvez pas configurer PyCharm pour exécuter vos tests, essayez de poser une nouvelle question de débordement de pile (si vous ne trouvez pas de question existante sur ce sujet).Je n'ai pas encore la compréhension de la pythonologie nécessaire pour voir la manière prévue de partager du code entre des projets non liés sans un hack d'importation fraternelle / relative. Jusqu'à ce jour, c'est ma solution. Pour
examples
outests
pour importer des éléments..\api
, cela ressemblerait à:import sys.path import os.path # Import from sibling directory ..\api sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..") import api.api import api.api_key
la source
Pour les importations de packages frères, vous pouvez utiliser la méthode insert ou append du module [sys.path] [2] :
if __name__ == '__main__' and if __package__ is None: import sys from os import path sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) ) import api
Cela fonctionnera si vous lancez vos scripts comme suit:
D'autre part, vous pouvez également utiliser l'importation relative:
if __name__ == '__main__' and if __package__ is not None: import ..api.api
Dans ce cas vous devrez lancer votre script avec l' argument '-m' (notez que, dans ce cas, vous ne devez pas donner l' extension '.py' ):
Bien sûr, vous pouvez mélanger les deux approches, afin que votre script fonctionne quel que soit son nom:
if __name__ == '__main__': if __package__ is None: import sys from os import path sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) ) import api else: import ..api.api
la source
__file__
global donc j'ai dû utiliser ce qui suit:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Mais cela fonctionne dans n'importe quel répertoire maintenantTLDR
Cette méthode ne nécessite pas de setuptools, de hacks de chemin, d'arguments de ligne de commande supplémentaires ou de spécification du niveau supérieur du package dans chaque fichier de votre projet.
Créez simplement un script dans le répertoire parent de tout ce que vous appelez
__main__
et exécutez tout à partir de là. Pour plus d'explications, continuez à lire.Explication
Cela peut être accompli sans pirater un nouveau chemin ensemble, utiliser des arguments de ligne de commande supplémentaires ou ajouter du code à chacun de vos programmes pour reconnaître ses frères.
La raison pour laquelle cela échoue, comme je crois que cela a été mentionné auparavant, c'est que les programmes appelés ont leur
__name__
ensemble comme__main__
. Lorsque cela se produit, le script appelé accepte de se trouver au niveau supérieur du package et refuse de reconnaître les scripts dans les répertoires frères.Cependant, tout ce qui se trouve sous le niveau supérieur du répertoire reconnaîtra toujours TOUT AUTRE sous le niveau supérieur. Cela signifie que la SEULE chose que vous devez faire pour que les fichiers des répertoires frères se reconnaissent / s'utilisent mutuellement est de les appeler à partir d'un script dans leur répertoire parent.
Proof of Concept Dans un répertoire avec la structure suivante:
Main.py
contient le code suivant:import sib1.call as call def main(): call.Call() if __name__ == '__main__': main()
sib1 / call.py contient:
import sib2.callsib as callsib def Call(): callsib.CallSib() if __name__ == '__main__': Call()
et sib2 / callsib.py contient:
def CallSib(): print("Got Called") if __name__ == '__main__': CallSib()
Si vous reproduisez cet exemple, vous remarquerez que l'appel
Main.py
entraînera l' affichage de "Got Called" tel qu'il est défini danssib2/callsib.py
même s'il asib2/callsib.py
été appelésib1/call.py
. Cependant, si l'on devait appeler directementsib1/call.py
(après avoir apporté les modifications appropriées aux importations), il lève une exception. Même s'il a fonctionné lorsqu'il est appelé par le script dans son répertoire parent, il ne fonctionnera pas s'il se croit au niveau supérieur du package.la source
examples
ettests
répertoires importer à partir duapi
module et être exécuté à partir du commandline ? » Cette réponse ne fonctionnera pas avec la structure de dossiers OP.python main.py
qui appelle lesib1.call
, vous pouvez exécuterpython -m sib1.call
.J'ai fait un exemple de projet pour montrer comment j'ai géré cela, qui est en fait un autre hack sys.path comme indiqué ci-dessus. Exemple d'importation de frères Python , qui repose sur:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Cela semble être assez efficace tant que votre répertoire de travail reste à la racine du projet Python. Si quelqu'un déploie cela dans un environnement de production réel, ce serait formidable de savoir si cela fonctionne également.
la source
Vous devez regarder pour voir comment les instructions d'importation sont écrites dans le code associé. Si
examples/example_one.py
utilise l'instruction d'importation suivante:import api.api
... alors il s'attend à ce que le répertoire racine du projet se trouve dans le chemin système.
Le moyen le plus simple de prendre en charge cela sans aucun hacks (comme vous le dites) serait d'exécuter les exemples à partir du répertoire de niveau supérieur, comme ceci:
la source
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. J'ai aussi la même chose avecimport api.api
.Juste au cas où quelqu'un utilisant Pydev sur Eclipse se retrouverait ici: vous pouvez ajouter le chemin parent du frère (et donc le parent du module appelant) en tant que dossier de bibliothèque externe en utilisant Projet-> Propriétés et en définissant des bibliothèques externes dans le menu de gauche Pydev-PYTHONPATH . Ensuite, vous pouvez importer de votre frère, par exemple
from sibling import some_class
.la source
Je voulais commenter la solution fournie par np8 mais je n'ai pas assez de réputation, je vais donc simplement mentionner que vous pouvez créer un fichier setup.py exactement comme ils l'ont suggéré, puis le faire à
pipenv install --dev -e .
partir du répertoire racine du projet pour le transformer en une dépendance modifiable. Ensuite, vos importations absolues fonctionneront, par exemple,from api.api import foo
et vous n'aurez pas à vous soucier des installations à l'échelle du système.Documentation
la source
1.1 Utilisateur
1.1.1 about.py
1.1.2 init .py
1.2 Technologie
1.2.1 info.py
1.1.2 init .py
Maintenant, si vous souhaitez accéder au module about.py dans le package User , à partir du module info.py du package Tech, vous devez apporter le chemin cmd (dans Windows) à Project, c'est-à-dire ** C: \ Users \ Personal \ Desktop \ Project> ** selon l'exemple de package ci-dessus. Et à partir de ce chemin, vous devez entrer, python -m Package_name.module_name Par exemple pour le package ci-dessus, nous devons faire,
C: \ Users \ Personal \ Desktop \ Project> python -m Tech.info
Points de lutteur
la source
pour la question principale:
appeler le dossier frère comme module:
from .. importer le dossier frère
appelez a_file.py depuis le dossier frère comme module:
à partir de ..siblingfolder importer un_fichier
appelez une_fonction dans un fichier dans le dossier frère en tant que module:
from..siblingmodule.a_file import func_name_exists_in_a_file
Le moyen le plus simple.
allez dans le dossier lib / site-packages.
s'il existe un fichier 'easy_install.pth', modifiez-le simplement et ajoutez votre répertoire dans lequel vous avez le script que vous voulez faire comme module.
s'il n'existe pas, créez-en un ... et placez-y le dossier que vous voulez
après l'avoir ajouté ..., python percevra automatiquement ce dossier comme similaire à celui des packages de site et vous pourrez appeler chaque script de ce dossier ou sous-dossier en tant que module.
J'ai écrit ceci sur mon téléphone et j'ai du mal à le configurer pour que tout le monde soit à l'aise à lire.
la source
Tout d'abord, vous devez éviter d'avoir des fichiers avec le même nom que le module lui-même. Cela peut interrompre d'autres importations.
Lorsque vous importez un fichier, l'interpréteur vérifie d'abord le répertoire courant, puis recherche les répertoires globaux.
À l'intérieur
examples
outests
vous pouvez appeler:from ..api import api
la source
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
fichier au répertoire de niveau supérieur. Sinon, Python ne peut pas le traiter comme un module__name__
est à la__main__
place depackage.module
, Python ne peut pas voir son package parent, donc ne.
pointe vers rien.