Quelle est la meilleure structure de projet pour une application Python? [fermé]

730

Imaginez que vous souhaitiez développer une application de bureau utilisateur non triviale (pas Web) en Python. Quelle est la meilleure façon de structurer la hiérarchie des dossiers du projet?

Les fonctionnalités souhaitables sont la facilité de maintenance, la convivialité IDE, l'adéquation pour le branchement / la fusion du contrôle de source et la génération facile de packages d'installation.

En particulier:

  1. Où mettez-vous la source?
  2. Où placez-vous les scripts de démarrage de l'application?
  3. Où placez-vous le projet IDE?
  4. Où placez-vous les tests unitaires / d'acceptation?
  5. Où placez-vous les données non-Python telles que les fichiers de configuration?
  6. Où placez-vous les sources non-Python telles que C ++ pour les modules d'extension binaires pyd / so?
kbluck
la source

Réponses:

378

Peu importe. Tout ce qui vous rend heureux fonctionnera. Il n'y a pas beaucoup de règles idiotes car les projets Python peuvent être simples.

  • /scriptsou /binpour ce genre de choses d'interface de ligne de commande
  • /tests pour vos tests
  • /lib pour vos bibliothèques en langage C
  • /doc pour la plupart de la documentation
  • /apidoc pour les documents API générés par Epydoc.

Et le répertoire de niveau supérieur peut contenir des fichiers README, Config et autres.

Le choix difficile est d'utiliser ou non un /srcarbre. Python ne dispose pas de distinction entre /src, /libet /bincomme a Java ou C.

Puisqu'un /srcrépertoire de niveau supérieur est considéré par certains comme dénué de sens, votre répertoire de niveau supérieur peut être l'architecture de niveau supérieur de votre application.

  • /foo
  • /bar
  • /baz

Je recommande de mettre tout cela sous le répertoire "nom-de-mon-produit". Donc, si vous écrivez une application nommée quux, le répertoire qui contient tout cela est nommé /quux.

Un autre projet PYTHONPATHpeut alors inclure la /path/to/quux/fooréutilisation du QUUX.foomodule.

Dans mon cas, depuis que j'utilise Komodo Edit, mon IDE cuft est un seul fichier .KPF. En fait, je l'ai mis dans le /quuxrépertoire de niveau supérieur et je ne l'ai pas ajouté à SVN.

S.Lott
la source
23
Des projets open source en python que vous recommanderiez d'émuler leur structure de répertoires?
Lance Rushing
4
Regardez Django pour un bon exemple.
S.Lott
33
Je n'ai pas tendance à considérer Django comme un bon exemple - jouer des tours avec sys.path est un DQ instantané dans mon livre.
Charles Duffy
18
re "trucs": Django ajoute le parent du dossier racine du projet au sys.path, afin que les modules puissent être importés soit "depuis project.app.module import klass" soit "depuis app.module import klass".
Jonathan Hartley
3
Oh, j'adore cette astuce et je l'utilise maintenant. Je veux placer le module partagé dans un autre répertoire, et je ne veux pas installer le module à l'échelle du système, ni demander aux gens de modifier PYTHONPATH manuellement. À moins que les gens proposent quelque chose de mieux, je pense que c'est en fait la façon la plus propre de procéder.
Yongwei Wu
242

Selon la structure du système de fichiers de Jean-Paul Calderone d'un projet Python :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README
cmcginty
la source
23
Project/project/? Ah, le second est le nom du paquet.
Cees Timmerman
44
comment le fichier exécutable dans le dossier bin fait-il référence au module de projet? (Je ne pense pas que la syntaxe python le permette ../dans une instruction include)
ThorSummoner
8
@ThorSummoner Simple. Vous installez le package! ( pip install -e /path/to/Project)
Kroltan
22
Ce serait génial si quelqu'un fermait un échantillon de cette mise en page avec hello.py et hello-test.py et le rendait disponible pour nous newbs.
jeremyjjbrown
8
@Bloke Le noyau est l' -eindicateur, qui installe le package en tant que package modifiable, c'est-à-dire qu'il l'installe en tant que liens vers le dossier de projet réel. L'exécutable peut alors simplement import projectavoir accès au module.
Kroltan
232

Ce billet de blog de Jean-Paul Calderone est généralement donné comme réponse en #python sur Freenode.

Structure du système de fichiers d'un projet Python

Faire:

  • nommez le répertoire quelque chose lié à votre projet. Par exemple, si votre projet est nommé "Twisted", nommez le répertoire de niveau supérieur pour ses fichiers source Twisted. Lorsque vous libère, vous devez inclure un suffixe de numéro de version: Twisted-2.5.
  • créer un répertoire Twisted/binet y mettre vos exécutables, si vous en avez. Ne leur donnez pas d' .pyextension, même s'il s'agit de fichiers source Python. N'y mettez pas de code sauf une importation et un appel à une fonction principale définie ailleurs dans vos projets. (Légère ride: puisque sous Windows, l'interpréteur est sélectionné par l'extension de fichier, vos utilisateurs Windows veulent réellement l'extension .py. Donc, lorsque vous créez un package pour Windows, vous pouvez l'ajouter. Malheureusement, il n'y a pas de truc de distutils facile qui Je sais pour automatiser ce processus. Considérant que sur POSIX, l'extension .py n'est qu'une verrue, alors que sur Windows, le manque est un bug réel, si votre base d'utilisateurs comprend des utilisateurs Windows, vous pouvez opter pour avoir simplement le .py extension partout.)
  • Si votre projet est exprimable en tant que fichier source Python unique, placez-le dans le répertoire et nommez-le quelque chose lié à votre projet. Par exemple Twisted/twisted.py,. Si vous avez besoin de plusieurs fichiers source, créez un package à la place ( Twisted/twisted/, avec un vide Twisted/twisted/__init__.py) et placez-y vos fichiers source. Par exemple Twisted/twisted/internet.py,.
  • mettez vos tests unitaires dans un sous-package de votre package (note - cela signifie que l'option de fichier source Python unique ci-dessus était une astuce - vous avez toujours besoin d'au moins un autre fichier pour vos tests unitaires). Par exemple Twisted/twisted/test/,. Bien sûr, faites-en un package avec Twisted/twisted/test/__init__.py. Placer les tests dans des fichiers comme Twisted/twisted/test/test_internet.py.
  • ajoutez Twisted/READMEet Twisted/setup.pypour expliquer et installer votre logiciel, respectivement, si vous vous sentez bien.

Ne pas:

  • placez votre source dans un répertoire appelé srcou lib. Cela rend difficile l'exécution sans l'installation.
  • mettez vos tests en dehors de votre package Python. Cela rend difficile l'exécution des tests par rapport à une version installée.
  • créer un package qui n'a puis mettre tout votre code dans . Créez simplement un module au lieu d'un package, c'est plus simple.__init__.py__init__.py
  • essayez de trouver des hacks magiques pour rendre Python capable d'importer votre module ou package sans que l'utilisateur ajoute le répertoire le contenant à leur chemin d'importation (via PYTHONPATH ou un autre mécanisme). Vous ne gérerez pas correctement tous les cas et les utilisateurs se mettront en colère contre vous lorsque votre logiciel ne fonctionnera pas dans leur environnement.
Adrian
la source
25
C'était exactement ce dont j'avais besoin. "N'ESSAYEZ PAS de trouver des hacks magiques pour permettre à Python d'importer votre module ou package sans que l'utilisateur ajoute le répertoire le contenant à son chemin d'importation." Bon à savoir!
Jack O'Connor
1
Le fait est que cela ne mentionne pas la partie doc importante d'un projet où le placer.
lpapp
14
Confus à propos de "placer votre source dans un répertoire appelé src ou lib. Cela rend son exécution difficile sans installation.". Qu'est-ce qui serait installé? Est-ce le nom du répertoire qui cause le problème ou le fait qu'il s'agit d'un sous-répertoire?
Peter Ehrlich
4
"Certaines personnes affirmeront que vous devez distribuer vos tests au sein de votre module lui-même - je ne suis pas d'accord. Cela augmente souvent la complexité pour vos utilisateurs; de nombreuses suites de tests nécessitent souvent des dépendances et des contextes d'exécution supplémentaires." python-guide-pt-br.readthedocs.io/en/latest/writing/structure/…
endolith
2
"Cela rend difficile l'exécution sans l'installation." - c'est le point
Nick T
123

Découvrez Open Sourcing a Python Project de la bonne façon .

Permettez-moi d'extraire la partie mise en page du projet de cet excellent article:

Lors de la configuration d'un projet, la mise en page (ou la structure du répertoire) est importante pour bien faire les choses. Une mise en page raisonnable signifie que les contributeurs potentiels n'ont pas à passer une éternité à chercher un morceau de code; l'emplacement des fichiers est intuitif. Comme nous avons affaire à un projet existant, cela signifie que vous devrez probablement déplacer des éléments.

Commençons par le haut. La plupart des projets ont un certain nombre de fichiers de niveau supérieur (comme setup.py, README.md, requirements.txt, etc.). Il y a alors trois répertoires que chaque projet devrait avoir:

  • Un répertoire docs contenant la documentation du projet
  • Un répertoire nommé avec le nom du projet qui stocke le package Python réel
  • Un répertoire de test dans l'un des deux endroits
    • Sous le répertoire du package contenant le code de test et les ressources
    • En tant que répertoire de niveau supérieur autonome Pour avoir une meilleure idée de la façon dont vos fichiers doivent être organisés, voici un instantané simplifié de la mise en page de l'un de mes projets, sandman:
$ pwd
~/code/sandman
$ tree
.
|- LICENSE
|- README.md
|- TODO.md
|- docs
|   |-- conf.py
|   |-- generated
|   |-- index.rst
|   |-- installation.rst
|   |-- modules.rst
|   |-- quickstart.rst
|   |-- sandman.rst
|- requirements.txt
|- sandman
|   |-- __init__.py
|   |-- exception.py
|   |-- model.py
|   |-- sandman.py
|   |-- test
|       |-- models.py
|       |-- test_sandman.py
|- setup.py

Comme vous pouvez le voir, il existe des fichiers de niveau supérieur, un répertoire docs (généré est un répertoire vide où sphinx placera la documentation générée), un répertoire sandman et un répertoire de test sous sandman.

David C. Bishop
la source
4
Je fais cela, mais plus encore: j'ai un Makefile de haut niveau avec une cible 'env' qui automatise 'virtualenv env; ./env/bin/pip install -r requirements.txt; ./env/bin/python setup.py develop ', et aussi généralement une cible' test 'qui dépend de env et installe également les dépendances de test puis exécute py.test.
pjz
@pjz Pourriez-vous développer votre idée? Parlez-vous de mettre Makefileau même niveau que setup.py? Donc, si je vous comprends, make envautomatise correctement la création d'un nouveau venvet installez-y les packages ...?
St.Antario
@ St.Antario exactement. Comme mentionné, j'ai également généralement une cible de «test» pour exécuter les tests, et parfois une cible de «libération» qui regarde la balise actuelle et construit une roue et l'envoie à pypi.
pjz
19

Essayez de démarrer le projet en utilisant le modèle python_boilerplate . Il suit en grande partie les meilleures pratiques (par exemple celles ici ), mais est mieux adapté au cas où vous vous sentiriez prêt à diviser votre projet en plusieurs œufs à un moment donné (et croyez-moi, avec tout sauf les projets les plus simples, vous le ferez. situation courante est où vous devez utiliser une version modifiée localement de la bibliothèque de quelqu'un d'autre).

  • Où mettez-vous la source?

    • Pour les projets de taille décente, il est logique de diviser la source en plusieurs œufs. Chaque œuf irait sous une mise en page setuptools distincte sous PROJECT_ROOT/src/<egg_name>.
  • Où placez-vous les scripts de démarrage de l'application?

    • L'option idéale consiste à enregistrer le script de démarrage de l'application en tant que entry_pointdans l'un des œufs.
  • Où placez-vous le projet IDE?

    • Dépend de l'IDE. Beaucoup d'entre eux gardent leurs affaires à PROJECT_ROOT/.<something>la racine du projet, et c'est très bien.
  • Où placez-vous les tests unitaires / d'acceptation?

    • Chaque œuf a un ensemble de tests séparé, conservé dans son PROJECT_ROOT/src/<egg_name>/testsrépertoire. Personnellement, je préfère utiliser py.testpour les exécuter.
  • Où placez-vous les données non-Python telles que les fichiers de configuration?

    • Ça dépend. Il peut exister différents types de données non Python.
      • "Ressources" , c'est-à-dire des données qui doivent être regroupées dans un œuf. Ces données vont dans le répertoire d'oeufs correspondant, quelque part dans l'espace de noms du package. Il peut être utilisé via le pkg_resourcespackage de setuptools, ou depuis Python 3.7 via le importlib.resourcesmodule de la bibliothèque standard.
      • Les "fichiers de configuration" , c'est-à-dire les fichiers non Python qui doivent être considérés comme externes aux fichiers source du projet, mais doivent être initialisés avec certaines valeurs lorsque l'application démarre. Pendant le développement, je préfère conserver ces fichiers PROJECT_ROOT/config. Pour le déploiement, il peut y avoir différentes options. Sous Windows, on peut utiliser %APP_DATA%/<app-name>/config, sous Linux /etc/<app-name>ou /opt/<app-name>/config.
      • Fichiers générés , c'est-à-dire des fichiers qui peuvent être créés ou modifiés par l'application pendant l'exécution. Je préférerais les conserver PROJECT_ROOT/varpendant le développement et sous /varpendant le déploiement de Linux.
  • Où placez-vous les sources non-Python telles que C ++ pour les modules d'extension binaires pyd / so?
    • Dans PROJECT_ROOT/src/<egg_name>/native

La documentation irait généralement dans PROJECT_ROOT/docou PROJECT_ROOT/src/<egg_name>/doc(cela dépend si vous considérez certains des œufs comme des grands projets séparés). Certaines configurations supplémentaires seront dans des fichiers comme PROJECT_ROOT/buildout.cfget PROJECT_ROOT/setup.cfg.

KT.
la source
Merci pour une excellente réponse! Vous m'avez clarifié beaucoup de choses! Je n'ai qu'une question: les œufs peuvent-ils être imbriqués?
Shookie
Non, vous ne pouvez pas "imbriquer" des œufs dans le sens de stocker des fichiers .egg dans d'autres fichiers .egg et en espérant que cela serait d'une grande utilité [à moins que vous n'ayez quelque chose de vraiment bizarre]. Ce que vous pouvez faire, cependant, est de créer des œufs «virtuels» - des packages vides qui ne fournissent aucun code utile, mais répertorient les autres packages dans leurs listes de dépendances. De cette façon, lorsqu'un utilisateur tente d'installer un tel package, il installe récursivement de nombreux œufs dépendants.
KT.
@KT pouvez-vous nous expliquer un peu comment vous gérez les données générées? En particulier, comment distinguez-vous (dans le code) entre développement et déploiement? J'imagine que vous avez une base_data_locationvariable, mais comment la définissez-vous correctement?
cmyr
1
Je suppose que vous parlez de «données d'exécution» - quelque chose que les gens mettent souvent sous / var / packagename ou ~ / .packagename / var, etc. La plupart du temps, ces choix sont suffisants par défaut que vos utilisateurs ne veulent pas changer. Si vous souhaitez autoriser ce comportement à être ajusté, les options sont plutôt abondantes et je ne pense pas qu'il existe une meilleure pratique unique. Choix typiques: a) ~ / .packagename / configfile, b) exporter MY_PACKAGE_CONFIG = / chemin / vers / configfile c) options de ligne de commande ou paramètres de fonction d) combinaison de ceux-ci.
KT.
Notez qu'il est assez habituel d'avoir une classe de configuration singleton quelque part, qui gère votre logique de chargement de configuration préférée pour vous et peut-être même permet à l'utilisateur de modifier les paramètres au moment de l'exécution. En général, cependant, je pense que c'est une question qui mérite une question distincte (qui aurait pu être posée avant quelque part ici).
KT.
15

D'après mon expérience, c'est juste une question d'itération. Mettez vos données et votre code partout où vous pensez qu'ils vont. Il y a de fortes chances que vous vous trompiez de toute façon. Mais une fois que vous avez une meilleure idée de la façon dont les choses vont se former, vous êtes bien mieux placé pour faire ce genre de suppositions.

En ce qui concerne les sources d'extension, nous avons un répertoire Code sous trunk qui contient un répertoire pour python et un répertoire pour diverses autres langues. Personnellement, je suis plus enclin à essayer de mettre n'importe quel code d'extension dans son propre référentiel la prochaine fois.

Cela dit, je reviens à mon point de départ: n'en faites pas trop l'affaire. Mettez-le quelque part qui semble fonctionner pour vous. Si vous trouvez quelque chose qui ne fonctionne pas, il peut (et devrait) être modifié.

Jason Baker
la source
Oui. J'essaie d'être "Pythonique" à ce sujet: explicite vaut mieux qu'implicite. Les hiérarchies de répertoire sont lues / inspectées plus qu'elles ne sont écrites. Etc ..
eric
10

Les données non python sont mieux regroupées dans vos modules Python en utilisant la package_dataprise en charge de setuptools . Une chose que je recommande fortement est d'utiliser des packages d'espaces de noms pour créer des espaces de noms partagés que plusieurs projets peuvent utiliser - un peu comme la convention Java de placer des packages com.yourcompany.yourproject(et de pouvoir avoir un com.yourcompany.utilsespace de noms partagé ).

En ce qui concerne la ramification et la fusion, si vous utilisez un système de contrôle de source suffisamment bon, il gérera les fusions même via des renommages; Bazaar est particulièrement doué pour cela.

Contrairement à d'autres réponses ici, je suis +1 sur le fait d'avoir un srcrépertoire de niveau supérieur (avec docet testrépertoires à côté). Les conventions spécifiques pour les arborescences de répertoires de documentation varient en fonction de ce que vous utilisez; Sphinx , par exemple, a ses propres conventions que son outil de démarrage rapide prend en charge.

S'il vous plaît, veuillez utiliser setuptools et pkg_resources; cela rend beaucoup plus facile pour d'autres projets de s'appuyer sur des versions spécifiques de votre code (et pour que plusieurs versions soient installées simultanément avec différents fichiers non-code, si vous utilisez package_data).

Charles Duffy
la source