web-dev-qa-db-fra.com

Comment configurer __main__.py, __init__.py et __setup__.py pour une configuration de paquet de base?

Contexte:

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. 

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

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

  3. 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. 

Question:

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'

Remarques:

J'ai lu 

29
Alex Lenail

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.

Résumer

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".


Modifier

Exécuter __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!).

Modifier 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.

Étendre le chemin dans __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')

É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 script 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

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.

17
a_guest

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.

3
MatTheWhale

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",]),
...)
0
Gribouillis