J'ai une structure de répertoire comme celle-ci:
Package/
setup.py
src/
__init__.py
__main__.py
code.py
Je veux pouvoir exécuter le code de nombreuses manières différentes.
pip install Package
et ensuite python
et ensuite from Package import *
python -m Package
qui devrait faire la chose dans __main__.py
python __main__.py
qui devrait également faire la chose dans __main__.py
mais cette fois-ci, nous supposons que vous avez téléchargé le source plutôt que pip installing
.
Maintenant, j'ai les deux premiers à travailler, mais avec une configuration compliquée:
setup.py:
setup(
name='Package',
packages=['Package'],
package_dir={'Package': 'src'},
...
entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }
__init__.py:
from Package.code import .......
__main__.py:
from . import .......
Ce qui aurait plus de sens pour moi serait dans les deux cas d’écrire
from code import ........
mais cela me donne des erreurs d'importation.
Est-ce que la façon dont je l'ai est vraiment la seule façon?
Et surtout, comment puis-je prendre en charge le troisième cas d'utilisation? En ce moment, python __main__.py
jette
File "__main__.py", line 10, in <module>
from . import code
ImportError: cannot import name 'class defined in code.py'
J'ai lu
Vous avez presque tout ce dont vous avez besoin (même un peu plus)! J'irais avec la configuration suivante:
code.py:
foo = 1
__init__.py:
from .code import foo
Faire une importation relative ici parce que __init__.py
sera utilisé lors de l'importation du package complet. Notez que nous marquons explicitement l'importation comme relative en utilisant la syntaxe .
- car elle est requise pour Python 3 (et dans Python 2 si vous avez utilisé from __future__ import absolute_import
).
__main__.py:
from Package import foo
print('foo = ', foo)
Ceci est le script principal du paquet et nous utilisons donc une instruction absolue import
. En faisant cela, nous supposons que le paquet a été installé (ou au moins a été mis sur le chemin); et c'est ainsi que les colis devraient être traités! Vous pensez peut-être que cela entre en conflit avec votre troisième cas d'utilisation, mais en réalité il n'y a pas de raison que pas à pip install
lorsqu'il s'agit d'un package. Et ce n’est vraiment pas un gros problème (surtout lorsqu’on utilise un virtualenv
)!
Si vous souhaitez modifier les fichiers source et observer facilement les modifications en exécutant le fichier __main__.py
, vous pouvez simplement installer le package à l'aide du commutateur -e
("editable"): pip install -e .
(en supposant que vous êtes dans le répertoire Package
). Cependant, avec votre structure de répertoire actuelle, cela ne fonctionnera pas car le commutateur -e
placera un Egg-link
dans le répertoire contenant le fichier setup.py
; ce répertoire ne contient pas un paquet nommé Package
mais plutôt src
(j'ai une question à ce sujet _).
Au lieu de cela, si vous suivez la convention pour nommer le répertoire racine de la source d'un paquet après le paquet lui-même (c'est-à-dire Package
pour votre exemple), l'installation avec -e
ne pose aucun problème: Python trouve le paquet requis Package
dans répertoire correspondant:
$ tree Package/
Package/
├── setup.py
└── Package <-- Renamed "src" to "Package" because that's the package's name.
├── code.py
├── __init__.py
└── __main__.py
Cela vous permet également d'omettre la définition supplémentaire de package_dir={'Package': 'src'}
dans setup.py
.
Une remarque à propos de setup.py
: pour les trois cas d'utilisation que vous avez spécifiés, il n'est pas nécessaire de définir un point d'entrée. C'est-à-dire que vous pouvez ignorer la ligne entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }
. En envoyant un module __main__.py
, python -m Package
exécutera facilement le code dans ce module. Vous pouvez également ajouter une clause if supplémentaire:
def main():
print('foo = ', foo)
if __== '__main__':
main()
Le point d’entrée vous permet par contre d’exécuter directement le code dans __main__.main
à partir de la CLI; l'exécution de $ Package
exécutera le code correspondant.
L’essentiel est que j’utilise toujours pip install
pour traiter les paquets. Et pourquoi pas, surtout si vous avez déjà créé un fichier setup.py
? Si les modifications apportées au package doivent être appliquées "en temps réel", vous pouvez l'installer avec le commutateur -e
(vous devrez peut-être renommer le dossier src
, voir ci-dessus). Ainsi, votre troisième cas d'utilisation se lirait comme suit: "Téléchargez les sources et pip install (-e) Package
(au sein d'un virtualenv); vous pourrez alors exécuter python __main__.py
".
__main__.py
sans pip install
Si vous ne voulez pas installer le paquet via pip, mais que vous pouvez quand même exécuter le script __main__.py
, je continuerai quand même avec la configuration ci-dessus. Ensuite, nous devons nous assurer que la ou les instructions from Package import ...
réussissent toujours. Pour ce faire, vous pouvez étendre le chemin d’importation (notez que cela nécessite que le répertoire src
soit renommé en nom du paquet!).
PYTHONPATH
Pour Linux bash, vous pouvez définir le chemin Python comme suit:
export PYTHONPATH=$PYTHONPATH:/path/to/Package
Ou si vous êtes dans le même répertoire que __main__.py
:
export PYTHONPATH=$PYTHONPATH:`cd ..; pwd`
Bien sûr, il existe différentes manières pour différents systèmes d'exploitation.
__main__.py
Vous (ou plutôt votre collègue) pouvez ajouter les lignes suivantes en haut du script (avant les instructions from Package import ...
):
import sys
sys.path.append('/path/to/Package')
sitecustomize.py
Vous pouvez placer un module nommé sitecustomize.py
dans le répertoire lib/python3.5/site-packages/
de votre installation Python, qui contient les lignes suivantes:
import sys
sys.path.append('/path/to/Package')
main.py
de niveau supérieur séparéDonc, vous auriez la mise en page suivante:
$ tree Package/
Package/
├── main.py <-- Add this file.
├── setup.py
└── src
├── code.py
├── __init__.py
└── __main__.py
où main.py
contient
import src.__main__
Maintenant, __main__.py
est considéré comme faisant partie du package src
et l'importation relative fonctionnera . Au lieu d'exécuter python src/__main__.py
, exécutez python main.py
maintenant.
from code import .........
échoue car aucun paquet Python installé sur votre système nommé code
. Il y a un module Python sur votre système nommé code
, mais dans votre instruction d'importation, vous ne spécifiez pas le paquet dans lequel se trouve votre module code
.
Le but du fichier __init__.py
que vous avez dans src/
indique à Python que le répertoire src/
doit être traité comme un package Python, avec son contenu en tant que modules dans le package. Puisque code.py
se trouve dans src/
avec votre fichier __init__.py
, votre module code
se trouve dans votre paquet src
.
Maintenant que vous savez dans quel paquet votre module code
peut être trouvé, vous pouvez en importer des éléments avec:
from src.code import .........
Notez également que le __init__.py
fait son travail simplement en étant présent dans votre répertoire src/
, de sorte qu'il n'a même pas besoin de contenir de code. Pour cette raison, il est généralement judicieux de laisser le fichier __init__.py
vide.
J'utilise souvent cette configuration parce qu'elle fonctionne mieux avec python setup.py develop
Package_root/
setup.py
src/
Package/
__init__.py
__main__.py
code.py
Ce n'est probablement pas (encore) la réponse détaillée à laquelle vous vous attendez, mais je pense que cela vaut la peine d'essayer pour les trois cas d'utilisation.
setup( ...
package_dir = {'': 'src'},
entry_points = {'console_scripts': ['Package = Package.__main__:main'],},
packages = find_packages(exclude=["Package.Egg_info",]),
...)