Qual è la migliore struttura di progetto per un'applicazione Python? [chiuso]


730

Immagina di voler sviluppare un'applicazione desktop non banale per l'utente finale (non web) in Python. Qual è il modo migliore per strutturare la gerarchia di cartelle del progetto?

Le caratteristiche desiderabili sono facilità di manutenzione, facilità di IDE, idoneità per la diramazione / fusione del controllo del codice sorgente e generazione semplice di pacchetti di installazione.

In particolare:

  1. Dove metti la fonte?
  2. Dove metti gli script di avvio dell'applicazione?
  3. Dove metti il ​​progetto IDE come cruft?
  4. Dove metti i test unitari / di collaudo?
  5. Dove metti i dati non Python come i file di configurazione?
  6. Dove mettete i sorgenti non Python come C ++ per i moduli di estensione binari pyd / so?

Risposte:


378

Non importa molto. Qualunque cosa ti renda felice funzionerà. Non ci sono molte regole sciocche perché i progetti Python possono essere semplici.

  • /scriptso /binper quel tipo di elementi dell'interfaccia della riga di comando
  • /tests per i tuoi test
  • /lib per le tue librerie in linguaggio C.
  • /doc per la maggior parte della documentazione
  • /apidoc per i documenti API generati da Epydoc.

E la directory di livello superiore può contenere file README, Config e quant'altro.

La scelta difficile è se usare o meno un /srcalbero. Python non hanno una distinzione tra /src, /libe /bincome Java o C ha.

Poiché una /srcdirectory di livello superiore è vista da alcuni come priva di significato, la directory di livello superiore può essere l'architettura di livello superiore dell'applicazione.

  • /foo
  • /bar
  • /baz

Consiglio di mettere tutto questo nella directory "name-of-my-product". Quindi, se stai scrivendo un'applicazione denominata quux, viene denominata la directory che contiene tutto questo materiale /quux.

Un altro progetto PYTHONPATH, quindi, può includere il /path/to/quux/fooriutilizzo del QUUX.foomodulo.

Nel mio caso, poiché utilizzo Komodo Edit, il mio IDE cuft è un singolo file .KPF. L'ho effettivamente inserito nella /quuxdirectory di livello superiore e ho omesso di aggiungerlo a SVN.


23
Qualche progetto open source su Python consiglieresti di emulare la loro struttura di directory?
Lance Rushing,

4
Guarda Django per un buon esempio.
S. Lott,

33
Non tendo a considerare Django un buon esempio: giocare brutti scherzi con sys.path è un DQ istantaneo nel mio libro.
Charles Duffy,

18
ri "trucchi": Django aggiunge il genitore della cartella del progetto principale a sys.path, in modo che i moduli possano essere importati come "da project.app.module import klass" o "da app.module import klass".
Jonathan Hartley

3
Oh, adoro questo trucco e lo sto usando ora. Voglio mettere il modulo condiviso in un'altra directory e non voglio installare il modulo a livello di sistema, né voglio chiedere alle persone di modificare manualmente PYTHONPATH. A meno che la gente non proponga qualcosa di meglio, penso che questo sia in realtà il modo più pulito di procedere.
Yongwei Wu,

242

Secondo la struttura del filesystem di Jean-Paul Calderone di un progetto Python :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README

23
Project/project/? Ah, il secondo è il nome del pacchetto.
Cees Timmerman,

44
come fa il file eseguibile nella cartella bin a fare riferimento al modulo di progetto? (Non credo che la sintassi di Python lo consenta ../in un'istruzione include)
ThorSummoner

8
@ThorSummoner Simple. Installi il pacchetto! ( pip install -e /path/to/Project)
Kroltan,

22
Sarebbe fantastico se qualcuno comprimesse un campione di questo layout con hello.py e hello-test.py e lo rendesse disponibile per noi newb.
jeremyjjbrown,

8
@Bloke Il core è il -eflag, che installa il pacchetto come pacchetto modificabile, ovvero lo installa come link alla cartella del progetto attuale. L'eseguibile può quindi solo import projectavere accesso al modulo.
Kroltan

232

Questo post sul blog di Jean-Paul Calderone è comunemente dato come risposta in #python su Freenode.

Struttura del filesystem di un progetto Python

Fare:

  • dai un nome alla directory in relazione al tuo progetto. Ad esempio, se il tuo progetto è denominato "Twisted", dai un nome alla directory di livello superiore per i suoi file di origine Twisted. Quando si esegue versioni, si dovrebbe includere un numero suffisso di versione: Twisted-2.5.
  • crea una directory Twisted/bine metti lì i tuoi eseguibili, se ne hai. Non dare loro .pyun'estensione, anche se sono file sorgente di Python. Non inserire alcun codice al loro interno tranne un'importazione di e chiamare una funzione principale definita altrove nei progetti. (Leggera ruga: poiché su Windows, l'interprete è selezionato dall'estensione del file, i tuoi utenti Windows in realtà vogliono l'estensione .py. Quindi, quando crei un pacchetto per Windows, potresti volerlo aggiungere. Sfortunatamente non ci sono trucchi facili da distinguere che So automatizzare questo processo. Considerando che su POSIX l'estensione .py è solo una verruca, mentre su Windows la mancanza è un vero e proprio bug, se la tua base di utenti include utenti Windows, potresti voler scegliere di avere solo il .py estensione ovunque.)
  • Se il tuo progetto è espressibile come un singolo file sorgente Python, inseriscilo nella directory e assegnagli un nome relativo al progetto. Ad esempio Twisted/twisted.py,. Se hai bisogno di più file sorgente, crea un pacchetto ( Twisted/twisted/, con uno vuoto Twisted/twisted/__init__.py) e inserisci i tuoi file sorgente. Ad esempio Twisted/twisted/internet.py,.
  • metti i tuoi test unit in un sotto-pacchetto del tuo pacchetto (nota - questo significa che l'unica opzione del file sorgente Python sopra era un trucco - hai sempre bisogno di almeno un altro file per i tuoi test unit). Ad esempio Twisted/twisted/test/,. Naturalmente, rendilo un pacchetto con Twisted/twisted/test/__init__.py. Collocare test in file come Twisted/twisted/test/test_internet.py.
  • aggiungi Twisted/READMEe Twisted/setup.pyper spiegare e installare il tuo software, rispettivamente, se ti senti bene.

Non:

  • metti la tua fonte in una directory chiamata srco lib. Questo rende difficile l'esecuzione senza installazione.
  • mettere i test fuori dal pacchetto Python. Ciò rende difficile eseguire i test con una versione installata.
  • crea un pacchetto che ha solo un __init__.pye poi inserisci tutto il tuo codice __init__.py. Basta creare un modulo anziché un pacchetto, è più semplice.
  • prova a trovare degli hack magici per rendere Python in grado di importare il tuo modulo o pacchetto senza che l'utente aggiunga la directory che lo contiene al loro percorso di importazione (tramite PYTHONPATH o qualche altro meccanismo). Sarà non gestire correttamente tutti i casi e gli utenti sarà possibile ottenere arrabbiato con te quando il software non funziona nel loro ambiente.

25
Questo era esattamente ciò di cui avevo bisogno. "NON tentare di inventare hack magici per rendere Python in grado di importare il tuo modulo o pacchetto senza che l'utente aggiunga la directory che lo contiene al loro percorso di importazione." Buono a sapersi!
Jack O'Connor,

1
Il fatto è che questo non menziona l'importante parte doc di un progetto dove posizionarlo.
lpapp,

14
Confuso su "metti il ​​tuo sorgente in una directory chiamata src o lib. Questo rende difficile l'esecuzione senza installazione.". Cosa sarebbe installato? È il nome della directory che causa il problema o il fatto che si tratta di un subdir?
Peter Ehrlich,

4
"Alcune persone affermeranno che dovresti distribuire i tuoi test all'interno del tuo modulo stesso - non sono d'accordo. Spesso aumenta la complessità per i tuoi utenti; molte suite di test spesso richiedono dipendenze e contesti di runtime aggiuntivi." python-guide-pt-br.readthedocs.io/en/latest/writing/structure/...
endolith

2
"Questo rende difficile l'esecuzione senza installazione." - questo è il punto
Nick T

123

Dai un'occhiata a Open Sourcing a Python Project nel modo giusto .

Vorrei estrarre la parte del layout del progetto di quell'eccellente articolo:

Quando si configura un progetto, il layout (o la struttura delle directory) è importante per avere ragione. Un layout ragionevole significa che i potenziali collaboratori non devono spendere per sempre a cercare un pezzo di codice; le posizioni dei file sono intuitive. Dato che abbiamo a che fare con un progetto esistente, significa che probabilmente dovrai spostare alcune cose.

Cominciamo dall'alto. La maggior parte dei progetti ha un numero di file di livello superiore (come setup.py, README.md, requisit.txt, ecc.). Esistono quindi tre directory che ogni progetto dovrebbe avere:

  • Una directory di documenti contenente la documentazione di progetto
  • Una directory denominata con il nome del progetto che memorizza il pacchetto Python effettivo
  • Una directory di prova in una delle due posizioni
    • Nella directory del pacchetto contenente codice di test e risorse
    • Come directory autonoma di primo livello Per avere un'idea più precisa dell'organizzazione dei file, ecco un'istantanea semplificata del layout di uno dei miei progetti, 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

Come puoi vedere, ci sono alcuni file di livello superiore, una directory docs (generata è una directory vuota in cui sphinx inserirà la documentazione generata), una directory sandman e una directory di test in sandman.


4
Lo faccio, ma ancora di più: ho un Makefile di livello superiore con un target 'env' che automatizza 'virtualenv env; ./env/bin/pip install -r Requisiti.txt; ./env/bin/python setup.py develop ', e di solito anche un target' test 'che dipende da env e installa anche dipendenze di test e quindi esegue py.test.
pjz,

@pjz La prego di espandere la tua idea? Stai parlando di mettere Makefileallo stesso livello di setup.py? Quindi, se capisco che make envautomatizza correttamente la creazione di un nuovo venve l'installazione dei pacchetti in esso ...?
Sant'Antario,

@ Sant'Antario esattamente. Come accennato, in genere ho anche un obiettivo di "test" per eseguire i test, e talvolta un obiettivo di "rilascio" che guarda il tag corrente e costruisce una ruota e lo invia a Pypi.
pjz


19

Prova ad avviare il progetto usando il modello python_boilerplate . Segue in gran parte le migliori pratiche (ad esempio quelle qui ), ma è più adatto nel caso in cui ti trovi disposto a dividere il tuo progetto in più di un uovo ad un certo punto (e credimi, con qualsiasi cosa tranne i progetti più semplici, lo farai. Uno situazione comune è dove devi usare una versione modificata localmente della libreria di qualcun altro).

  • Dove metti la fonte?

    • Per progetti abbastanza grandi ha senso dividere la fonte in più uova. Ogni uovo andrebbe come un layout setuptools separato sotto PROJECT_ROOT/src/<egg_name>.
  • Dove metti gli script di avvio dell'applicazione?

    • L'opzione ideale è che lo script di avvio dell'applicazione sia registrato come entry_pointin una delle uova.
  • Dove metti il ​​progetto IDE come cruft?

    • Dipende dall'IDE. Molti di loro tengono le loro cose PROJECT_ROOT/.<something>alla radice del progetto, e questo va bene.
  • Dove metti i test unitari / di collaudo?

    • Ogni uovo ha un set separato di test, tenuto nella sua PROJECT_ROOT/src/<egg_name>/testsdirectory. Personalmente preferisco usare py.testper eseguirli.
  • Dove metti i dati non Python come i file di configurazione?

    • Dipende. Possono esserci diversi tipi di dati non Python.
      • "Risorse" , ovvero dati che devono essere impacchettati all'interno di un uovo. Questi dati vanno nella directory uovo corrispondente, da qualche parte all'interno dello spazio dei nomi del pacchetto. Può essere utilizzato tramite il pkg_resourcespacchetto da setuptools, o poiché Python 3.7 tramite il importlib.resourcesmodulo dalla libreria standard.
      • "File di configurazione" , ovvero file non Python che devono essere considerati esterni ai file di origine del progetto, ma devono essere inizializzati con alcuni valori all'avvio dell'applicazione. Durante lo sviluppo preferisco conservare tali file PROJECT_ROOT/config. Per la distribuzione ci possono essere varie opzioni. Su Windows è possibile utilizzare %APP_DATA%/<app-name>/config, su Linux /etc/<app-name>o /opt/<app-name>/config.
      • File generati , ovvero file che possono essere creati o modificati dall'applicazione durante l'esecuzione. Preferirei tenerli attivi PROJECT_ROOT/vardurante lo sviluppo e /vardurante la distribuzione Linux.
  • Dove mettete i sorgenti non Python come C ++ per i moduli di estensione binari pyd / so?
    • In PROJECT_ROOT/src/<egg_name>/native

In genere la documentazione verrebbe approfondita PROJECT_ROOT/doco PROJECT_ROOT/src/<egg_name>/doc(questo dipende dal fatto che alcune uova vengano considerate progetti separati di grandi dimensioni). Alcune configurazioni aggiuntive saranno in file come PROJECT_ROOT/buildout.cfge PROJECT_ROOT/setup.cfg.


Grazie per un'ottima risposta! Mi hai chiarito molte cose! Ho solo una domanda: le uova possono essere nidificate?
Shookie,

No, non è possibile "nidificare" le uova nel senso di archiviare i file .egg all'interno di altri file .egg e sperare che ciò possa essere di grande utilità [a meno che non si tratti di qualcosa di veramente strano]. Quello che puoi fare, però, è creare uova "virtuali" - pacchetti vuoti che non forniscono alcun codice utile, ma elencano altri pacchetti nei loro elenchi di dipendenze. In questo modo, quando un utente tenta di installare un tale pacchetto, installerà in modo ricorsivo molte uova dipendenti.
KT.

@KT puoi approfondire un po 'come gestisci i dati generati? In particolare, come si distingue (nel codice) tra sviluppo e distribuzione? Immagino che tu abbia qualche base_data_locationvariabile, ma come la imposti in modo appropriato?
cmyr,

1
Presumo che tu stia parlando di "dati di runtime" - qualcosa che le persone spesso inseriscono in / var / nomepacchetto o ~ / .p nomepacchetto / var o quant'altro. Il più delle volte queste scelte sono sufficienti come impostazione predefinita che i tuoi utenti non vogliono cambiare. Se si desidera consentire la messa a punto di questo comportamento, le opzioni sono piuttosto abbondanti e non credo che esista una migliore pratica adatta a tutti. Scelte tipiche: a) ~ / .packagename / configfile, b) esportazione MY_PACKAGE_CONFIG = / path / in / configfile c) opzioni della riga di comando o parametri di funzione d) combinazione di questi.
KT.

Si noti che è abbastanza comune avere una classe di configurazione singleton da qualche parte, che gestisce la logica di caricamento della configurazione preferita per te e forse consente persino all'utente di modificare le impostazioni in fase di esecuzione. In generale, tuttavia, penso che questo sia un problema che merita una domanda separata (che potrebbe essere stata posta prima da qualche parte qui).
KT.

15

Nella mia esperienza, è solo una questione di iterazione. Inserisci i tuoi dati e il codice ovunque tu pensi che vadano. È probabile che ti sbagli comunque. Ma una volta che hai un'idea migliore di come andranno le cose, sarai in una posizione molto migliore per fare questo tipo di ipotesi.

Per quanto riguarda le fonti di estensione, abbiamo una directory Code sotto trunk che contiene una directory per Python e una directory per varie altre lingue. Personalmente, sono più propenso a provare a mettere qualsiasi codice di estensione nel suo repository la prossima volta.

Detto questo, torno al mio punto iniziale: non fare un affare troppo grande. Mettilo da qualche parte che sembra funzionare per te. Se trovi qualcosa che non funziona, può (e dovrebbe) essere cambiato.


Sì. Cerco di essere "Pythonic" al riguardo: esplicito è meglio che implicito. Le gerarchie di directory vengono lette / controllate più di quanto siano scritte. Ecc.
eric,

10

I dati non Python vengono raggruppati al meglio all'interno dei moduli Python utilizzando il package_datasupporto in setuptools . Una cosa che consiglio vivamente è l'uso di pacchetti di spazi dei nomi per creare spazi dei nomi condivisi che possono essere utilizzati da più progetti, proprio come la convenzione Java di inserire i pacchetti com.yourcompany.yourproject(e di poter avere uno com.yourcompany.utilsspazio dei nomi condiviso ).

Per quanto riguarda la ramificazione e l'unione, se si utilizza un sistema di controllo del codice sorgente abbastanza buono, gestirà le fusioni anche attraverso i nomi; Bazaar è particolarmente bravo in questo.

Contrariamente ad altre risposte qui, sono +1 per avere una srcdirectory di primo livello (con doce testdirectory a fianco). Convenzioni specifiche per gli alberi delle directory della documentazione varieranno a seconda di ciò che si sta utilizzando; Sphinx , ad esempio, ha le sue convenzioni supportate dal suo strumento di avvio rapido.

Per favore, sfrutta setuptools e pkg_resources; questo rende molto più facile per altri progetti fare affidamento su versioni specifiche del tuo codice (e se più versioni devono essere installate simultaneamente con diversi file non di codice, se lo stai utilizzando package_data).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.