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.

  1. pip install Package et puis python et puis from Package import *

  2. python -m Package qui devrait faire la chose dans __main__.py

  3. 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 que pip 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.
26
demandé sur Alex Lenail 2017-07-07 21:11:26

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

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.

13
répondu a_guest 2018-03-16 15:50:35

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.

3
répondu MatTheWhale 2017-07-07 19:20:42

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",]),
...)
0
répondu Gribouillis 2017-07-16 09:59:00