Comment configurer main.py, init.py, et setup.py pour une configuration de base du paquet?
Contexte:
j'ai une structure de répertoire:
Package/
setup.py
src/
__init__.py
__main__.py
code.py
je veux pouvoir exécuter le code de plusieurs façons.
-
pip install Package
et puispython
et puisfrom Package import *
-
python -m Package
qui devrait faire la chose dans__main__.py
-
python __main__.py
qui devrait aussi faire la chose dans__main__.py
mais cette fois, nous supposons que vous avez téléchargé source plutôt quepip installing
.
maintenant j'ai obtenu les deux premiers à travailler, mais avec une configuration messy:
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 le plus de sens pour moi serait dans les deux cas d'écrire
from code import ........
mais cela me donne des erreurs d'importation.
Question:
Est la façon dont je l'ai vraiment la seule façon?
et le plus important, Comment puis-je soutenir le cas de la troisième utilisation? En ce moment, python __main__.py
lance
File "__main__.py", line 10, in <module>
from . import code
ImportError: cannot import name 'class defined in code.py'
Notes:
j'ai lu
- https://chriswarrick.com/blog/2014/09/15/python-apps-the-right-way-entry_points-and-scripts /
- http://setuptools.readthedocs.io/en/latest/setuptools.html
- les nombreuses questions ici qui ressemblent à celui-ci, mais ne répondent pas à ma question ci-dessus.
3 réponses
Vous avez presque tout ce dont vous avez besoin (même un peu plus)! Je dirais la configuration suivante:
code.py :
foo = 1
__init__.py:
from .code import foo
fait une importation relative ici parce que __init__.py
sera utilisé lors de l'importation du paquet entier. Notez que nous marquons explicitement l'importation comme relative en utilisant la syntaxe .
parce que cette est nécessaire pour Python 3 (et en Python 2 Si vous avez fait from __future__ import absolute_import
).
__main__.py:
from Package import foo
print('foo = ', foo)
c'est le script principal du paquet et nous utilisons donc une instruction absolue import
. Ce faisant, nous supposons que le paquet a été installé (ou au moins a été mis sur le chemin); et c'est la façon dont les paquets doivent être traitées avec! Vous pourriez penser que cela entre en conflit avec votre troisième cas d'utilisation, mais en fait il n'est pas une raison pas à pip install
lorsqu'il s'agit d'un colis. Et ce n'est vraiment pas une grosse affaire (surtout quand on utilise un virtualenv
)!
si votre préoccupation est de bricoler avec les fichiers source et d'observer facilement les changements en lançant le fichier __main__.py
, alors vous pouvez simplement installer le paquet en utilisant le commutateur -e
("modifiable"): pip install -e .
(en supposant que vous êtes dans le répertoire Package
). Avec votre structure actuelle du répertoire, cependant, cela ne fonctionnera pas parce que 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 src
à la place (j'ai une question à propos de ce ).
à la place, si vous suivez la convention pour nommer le répertoire racine du Source d'un paquet après le paquet lui-même (c'est-à-dire Package
pour votre exemple) puis installer avec -e
n'est pas un problème: Python trouve le paquet requis Package
dans le 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 note à 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 que vous pouvez sauter la ligne entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }
. En envoyant un __main__.py
module 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 __name__ == '__main__':
main()
par contre, le point d'entrée vous permet d'exécuter directement le code dans __main__.main
à partir du CLI; c'est-à-dire que $ Package
exécutera le code correspondant.
Récapitulation
le fait est que j'ai toujours utilisé pip install
quand traiter avec des paquets. Et pourquoi pas, surtout si vous avez déjà créé un setup.py
fichier? Si des modifications au paquet doivent être appliquées "en temps réel", vous pouvez installer avec le commutateur -e
(cela pourrait nécessiter un changement de nom du dossier src
, voir ci-dessus). Ainsi votre troisième cas d'utilisation se lirait comme "Télécharger la source et pip install (-e) Package
(dans un virtualenv); alors vous pouvez lancer python __main__.py
".
Modifier
Exécuter __main__.py
sans pip install
si vous ne voulez pas installer le paquet via pip mais que vous pouvez tout de même exécuter le script __main__.py
, j'opterais quand même pour la configuration ci-dessus. Ensuite, nous devons nous assurer que la(Les) déclaration (s) from Package import ...
est (sont) toujours en vigueur et cela peut être réalisé en étendant le chemin d'importation (notez que cela nécessite que le répertoire src
soit renommé au nom du paquet!).
modifier PYTHONPATH
Pour Linux bash vous pouvez définir le Pythonpath 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 y a différentes façons pour différents systèmes d'exploitation.
étendre le chemin dans __main__.py
(Vous, ou plutôt votre collègue) pourrait ajouter les lignes suivantes en haut du script (avant les déclarations from Package import ...
):
import sys
sys.path.append('/path/to/Package')
étendre le chemin dans 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')
créer un séparé, de haut niveau main.py
script
donc vous avez la disposition 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 traité comme une partie du paquet src
et l'importation relative fonctionnera.
Au lieu d'exécuter python src/__main__.py
vous exécuteriez python main.py
maintenant.
from code import .........
échoue parce qu'il n'y a pas de paquet Python installé sur votre système nommé code
. Il y a un python module sur votre système appelé code
, mais dans votre déclaration d'importation vous ne spécifiez pas le paquet dans lequel votre code
module peut être trouvé.
le but du fichier __init__.py
que vous avez dans src/
dit à Python que le répertoire src/
doit être traité comme un paquet Python, avec son contenu en tant que modules dans le paquet. Puisque code.py
est situé dans src/
avec votre fichier __init__.py
, votre module code
est situé dans votre paquet src
.
maintenant que vous savez dans quel paquet se trouve votre module code
, vous pouvez en importer des articles avec:
from src.code import .........
aussi, comme une note secondaire: le __init__.py
fait son travail juste en étant présent dans votre répertoire src/
, ainsi il n'a même pas besoin de contenir tout le code. Pour cette raison, c'est généralement une bonne idée de laisser le fichier __init__.py
vierge.
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 que 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",]),
...)