Was ist die beste Projektstruktur für eine Python-Anwendung? [geschlossen]

730

Stellen Sie sich vor, Sie möchten eine nicht triviale Desktop-Anwendung für Endbenutzer (nicht für das Web) in Python entwickeln. Wie lässt sich die Ordnerhierarchie des Projekts am besten strukturieren?

Wünschenswerte Funktionen sind einfache Wartung, IDE-Freundlichkeit, Eignung für das Verzweigen / Zusammenführen der Quellcodeverwaltung und die einfache Generierung von Installationspaketen.

Bestimmtes:

  1. Woher nimmst du die Quelle?
  2. Wo platzieren Sie Anwendungsstart-Skripte?
  3. Wo platzieren Sie die IDE-Projekt-Cruft?
  4. Wo platzieren Sie die Einheits- / Abnahmetests?
  5. Wo legen Sie Nicht-Python-Daten wie Konfigurationsdateien ab?
  6. Wo platzieren Sie Nicht-Python-Quellen wie C ++ für binäre pyd / so-Erweiterungsmodule?
kbluck
quelle

Antworten:

378

Ist nicht zu wichtig. Was dich glücklich macht, wird funktionieren. Es gibt nicht viele dumme Regeln, weil Python-Projekte einfach sein können.

  • /scriptsoder /binfür diese Art von Kommandozeilenschnittstellenmaterial
  • /tests für Ihre Tests
  • /lib für Ihre C-Sprachbibliotheken
  • /doc für die meisten Dokumentationen
  • /apidoc für die von Epydoc generierten API-Dokumente.

Und das Verzeichnis der obersten Ebene kann README's, Config's und so weiter enthalten.

Die schwierige Wahl ist, ob Sie einen /srcBaum verwenden oder nicht . Python hat bisher keine Unterscheidung zwischen /src, /libund /binwie Java oder C hat.

Da ein /srcVerzeichnis der obersten Ebene von einigen als bedeutungslos angesehen wird, kann Ihr Verzeichnis der obersten Ebene die Architektur der obersten Ebene Ihrer Anwendung sein.

  • /foo
  • /bar
  • /baz

Ich empfehle, all dies unter das Verzeichnis "Name meines Produkts" zu stellen. Wenn Sie also eine Anwendung mit dem Namen schreiben quux, wird das Verzeichnis mit all diesen Inhalten benannt /quux.

Ein anderes Projekt PYTHONPATHkann dann /path/to/quux/foodie Wiederverwendung des QUUX.fooModuls umfassen.

In meinem Fall ist mein IDE-Cuft, da ich Komodo Edit verwende, eine einzelne .KPF-Datei. Ich habe das tatsächlich in das /quuxVerzeichnis der obersten Ebene gestellt und das Hinzufügen zu SVN weggelassen.

S.Lott
quelle
23
Gibt es Open Source Python-Projekte, die Sie empfehlen würden, um ihre Verzeichnisstruktur zu emulieren?
Lance Rushing
4
Schauen Sie sich Django als gutes Beispiel an.
S.Lott
33
Ich halte Django nicht für ein gutes Beispiel - Streiche mit sys.path zu spielen ist ein sofortiger DQ in meinem Buch.
Charles Duffy
18
Zu "Tricks": Django fügt dem sys.path das übergeordnete Element des Stammprojektordners hinzu, sodass Module entweder als "from project.app.module import klass" oder "from app.module import klass" importiert werden können.
Jonathan Hartley
3
Oh, ich liebe diesen Trick und benutze ihn jetzt. Ich möchte das freigegebene Modul in einem anderen Verzeichnis ablegen und das Modul weder systemweit installieren noch die Benutzer auffordern, PYTHONPATH manuell zu ändern. Wenn die Leute nichts Besseres vorschlagen, ist dies meiner Meinung nach der sauberste Weg.
Yongwei Wu
242

Nach Jean-Paul Calderones Dateisystemstruktur eines Python-Projekts :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README
cmcginty
quelle
23
Project/project/? Ah, der zweite ist der Paketname.
Cees Timmerman
44
Wie verweist die ausführbare Datei im Ordner bin auf das Projektmodul? (Ich glaube nicht, dass Python-Syntax ../in einer include-Anweisung erlaubt )
ThorSummoner
8
@ThorSummoner Einfach. Sie installieren das Paket! ( pip install -e /path/to/Project)
Kroltan
22
Es wäre fantastisch, wenn jemand ein Beispiel dieses Layouts mit hello.py und hello-test.py komprimieren und für uns Neulinge verfügbar machen würde.
Jeremyjjbrown
8
@Bloke Der Kern ist das -eFlag, das das Paket als bearbeitbares Paket installiert, dh als Links zum eigentlichen Projektordner installiert. Die ausführbare Datei kann dann nur import projectnoch Zugriff auf das Modul haben.
Kroltan
232

Dieser Blog-Beitrag von Jean-Paul Calderone wird häufig als Antwort in #python on Freenode gegeben.

Dateisystemstruktur eines Python-Projekts

Tun:

  • Benennen Sie das Verzeichnis mit Ihrem Projekt. Wenn Ihr Projekt beispielsweise "Twisted" heißt, benennen Sie das Verzeichnis der obersten Ebene für seine Quelldateien Twisted. Wenn Sie Releases erstellen, sollten Sie ein Versionsnummernsuffix einfügen : Twisted-2.5.
  • Erstellen Sie ein Verzeichnis Twisted/binund legen Sie Ihre ausführbaren Dateien dort ab, falls vorhanden. Geben Sie ihnen keine .pyErweiterung, auch wenn es sich um Python-Quelldateien handelt. Fügen Sie keinen Code ein, außer dem Importieren und Aufrufen einer Hauptfunktion, die an einer anderen Stelle in Ihren Projekten definiert ist. (Leichte Falten: Da unter Windows der Interpreter durch die Dateierweiterung ausgewählt wird, möchten Ihre Windows-Benutzer tatsächlich die Erweiterung .py. Wenn Sie also für Windows packen, möchten Sie sie möglicherweise hinzufügen. Leider gibt es keinen einfachen Trick für Distutils Ich weiß, wie man diesen Prozess automatisiert. Wenn man bedenkt, dass die Erweiterung .py unter POSIX nur eine Warze ist, während das Fehlen unter Windows ein tatsächlicher Fehler ist. Wenn Ihre Benutzerbasis Windows-Benutzer enthält, möchten Sie möglicherweise nur die .py verwenden Erweiterung überall.)
  • Wenn Ihr Projekt als einzelne Python-Quelldatei ausgedrückt werden kann, legen Sie es in dem Verzeichnis ab und benennen Sie es mit einem Namen, der mit Ihrem Projekt zusammenhängt. Zum Beispiel Twisted/twisted.py. Wenn Sie mehrere Quelldateien benötigen, erstellen Sie stattdessen ein Paket ( Twisted/twisted/mit einem leeren Twisted/twisted/__init__.py) und platzieren Sie Ihre Quelldateien darin. Zum Beispiel Twisted/twisted/internet.py.
  • Fügen Sie Ihre Komponententests in ein Unterpaket Ihres Pakets ein (Hinweis - dies bedeutet, dass die obige Option für eine einzelne Python-Quelldatei ein Trick war - Sie benötigen immer mindestens eine weitere Datei für Ihre Komponententests). Zum Beispiel Twisted/twisted/test/. Machen Sie es natürlich zu einem Paket mit Twisted/twisted/test/__init__.py. Platzieren Sie Tests in Dateien wie Twisted/twisted/test/test_internet.py.
  • Fügen Sie Ihre Software hinzu Twisted/READMEund Twisted/setup.pyerklären Sie sie, wenn Sie sich gut fühlen.

Nicht:

  • Legen Sie Ihre Quelle in einem Verzeichnis namens srcoder ab lib. Dies macht es schwierig, ohne Installation zu laufen.
  • Stellen Sie Ihre Tests außerhalb Ihres Python-Pakets. Dies macht es schwierig, die Tests für eine installierte Version auszuführen.
  • Erstellen Sie ein Paket, das nur ein enthält, __init__.pyund fügen Sie dann Ihren gesamten Code ein __init__.py. Machen Sie einfach ein Modul anstelle eines Pakets, es ist einfacher.
  • Versuchen Sie, magische Hacks zu entwickeln, damit Python Ihr Modul oder Paket importieren kann, ohne dass der Benutzer das Verzeichnis, das es enthält, zu seinem Importpfad hinzufügt (entweder über PYTHONPATH oder einen anderen Mechanismus). Sie werden nicht alle Fälle richtig behandeln und Benutzer werden wütend auf Sie, wenn Ihre Software in ihrer Umgebung nicht funktioniert.
Adrian
quelle
25
Das war genau das, was ich brauchte. "Versuchen Sie NICHT, magische Hacks zu entwickeln, damit Python Ihr Modul oder Paket importieren kann, ohne dass der Benutzer das Verzeichnis, das es enthält, zu seinem Importpfad hinzufügt." Gut zu wissen!
Jack O'Connor
1
Dies erwähnt nicht den wichtigen Dokumententeil eines Projekts, in dem es platziert werden soll.
lpapp
14
Verwirrt über "Legen Sie Ihre Quelle in einem Verzeichnis namens src oder lib ab. Dies macht es schwierig, sie ohne Installation auszuführen." Was würde installiert werden? Ist es der Verzeichnisname, der das Problem verursacht, oder die Tatsache, dass es sich überhaupt um ein Unterverzeichnis handelt?
Peter Ehrlich
4
"Einige Leute werden behaupten, dass Sie Ihre Tests innerhalb Ihres Moduls selbst verteilen sollten - ich bin anderer Meinung. Dies erhöht häufig die Komplexität für Ihre Benutzer. Viele Testsuiten erfordern häufig zusätzliche Abhängigkeiten und Laufzeitkontexte." python-guide-pt-br.readthedocs.io/en/latest/writing/structure/…
Endolith
2
"Dies macht es schwierig, ohne Installation zu laufen." - das ist der Punkt
Nick T
123

Schauen Sie sich Open Sourcing eines Python-Projekts richtig an .

Lassen Sie mich den Projektlayout- Teil dieses ausgezeichneten Artikels extrahieren:

Beim Einrichten eines Projekts ist das Layout (oder die Verzeichnisstruktur) wichtig, um die richtigen Ergebnisse zu erzielen. Ein vernünftiges Layout bedeutet, dass potenzielle Mitwirkende nicht ewig nach einem Code suchen müssen. Dateispeicherorte sind intuitiv. Da es sich um ein vorhandenes Projekt handelt, müssen Sie wahrscheinlich einige Dinge verschieben.

Beginnen wir oben. Die meisten Projekte verfügen über eine Reihe von Dateien der obersten Ebene (wie setup.py, README.md, require.txt usw.). Es gibt dann drei Verzeichnisse, die jedes Projekt haben sollte:

  • Ein Dokumentverzeichnis mit Projektdokumentation
  • Ein Verzeichnis mit dem Namen des Projekts, in dem das eigentliche Python-Paket gespeichert ist
  • Ein Testverzeichnis an einer von zwei Stellen
    • Unter dem Paketverzeichnis mit Testcode und Ressourcen
    • Als eigenständiges Verzeichnis der obersten Ebene Um einen besseren Überblick über die Organisation Ihrer Dateien zu erhalten, finden Sie hier eine vereinfachte Momentaufnahme des Layouts für eines meiner Projekte, 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

Wie Sie sehen können, gibt es einige Dateien der obersten Ebene, ein Dokumentverzeichnis (generiert ist ein leeres Verzeichnis, in dem Sphinx die generierte Dokumentation ablegt), ein Sandman-Verzeichnis und ein Testverzeichnis unter Sandman.

David C. Bishop
quelle
4
Ich mache das, aber mehr noch: Ich habe ein Makefile auf oberster Ebene mit einem 'env'-Ziel, das' virtualenv env 'automatisiert. ./env/bin/pip installiere -r Anforderungen.txt; ./env/bin/python setup.py Develop 'und normalerweise auch ein' Test'-Ziel, das von env abhängt und auch Testabhängigkeiten installiert und dann py.test ausführt.
pjz
@pjz Könnten Sie bitte Ihre Idee erweitern? Sprechen Sie über Makefiledas gleiche Niveau wie setup.py? Wenn ich also verstehe, dass Sie make envdas Erstellen eines neuen venvund das Installieren der Pakete korrekt automatisieren ...?
St.Antario
@ St.Antario genau. Wie bereits erwähnt, habe ich im Allgemeinen auch ein "Test" -Ziel, um die Tests auszuführen, und manchmal ein "Release" -Ziel, das das aktuelle Tag betrachtet, ein Rad baut und es an pypi sendet.
pjz
19

Versuchen Sie, das Projekt mit der Vorlage python_boilerplate zu starten . Es folgt weitgehend den Best Practices (z. B. die hier ), ist jedoch besser geeignet, wenn Sie bereit sind, Ihr Projekt irgendwann in mehr als ein Ei aufzuteilen (und glauben Sie mir, mit etwas anderem als den einfachsten Projekten werden Sie es tun In der Regel müssen Sie eine lokal geänderte Version der Bibliothek eines anderen Benutzers verwenden.

  • Woher nimmst du die Quelle?

    • Bei anständig großen Projekten ist es sinnvoll, die Quelle in mehrere Eier aufzuteilen. Jedes Ei würde als separates Setuptool-Layout unter gehen PROJECT_ROOT/src/<egg_name>.
  • Wo platzieren Sie Anwendungsstart-Skripte?

    • Die ideale Option besteht darin, das Startskript der Anwendung als entry_pointin einem der Eier registriert zu haben .
  • Wo platzieren Sie die IDE-Projekt-Cruft?

    • Kommt auf die IDE an. Viele von ihnen behalten ihre Sachen in PROJECT_ROOT/.<something>der Wurzel des Projekts, und das ist in Ordnung.
  • Wo platzieren Sie die Einheits- / Abnahmetests?

    • Jedes Ei hat eine separate Reihe von Tests, die in seinem PROJECT_ROOT/src/<egg_name>/testsVerzeichnis gespeichert sind. Ich persönlich bevorzuge es, sie py.testzu betreiben.
  • Wo legen Sie Nicht-Python-Daten wie Konfigurationsdateien ab?

    • Es hängt davon ab, ob. Es kann verschiedene Arten von Nicht-Python-Daten geben.
      • "Ressourcen" , dh Daten, die in einem Ei verpackt werden müssen. Diese Daten werden in das entsprechende Eierverzeichnis irgendwo im Paket-Namespace verschoben. Es kann über das pkg_resourcesPaket von setuptoolsoder seit Python 3.7 über das importlib.resourcesModul aus der Standardbibliothek verwendet werden.
      • "Konfigurationsdateien" , dh Nicht-Python-Dateien, die als außerhalb der Projektquelldateien betrachtet werden sollen, aber beim Start der Anwendung mit einigen Werten initialisiert werden müssen. Während der Entwicklung behalte ich solche Dateien lieber bei PROJECT_ROOT/config. Für die Bereitstellung gibt es verschiedene Optionen. Unter Windows kann man verwenden %APP_DATA%/<app-name>/config, unter Linux /etc/<app-name>oder /opt/<app-name>/config.
      • Generierte Dateien , dh Dateien, die von der Anwendung während der Ausführung erstellt oder geändert werden können. Ich würde es vorziehen, sie PROJECT_ROOT/varwährend der Entwicklung und /varwährend der Linux-Bereitstellung beizubehalten.
  • Wo platzieren Sie Nicht-Python-Quellen wie C ++ für binäre pyd / so-Erweiterungsmodule?
    • In PROJECT_ROOT/src/<egg_name>/native

Die Dokumentation wird normalerweise in PROJECT_ROOT/docoder PROJECT_ROOT/src/<egg_name>/doc(dies hängt davon ab, ob Sie einige der Eier als separate große Projekte betrachten). Einige zusätzliche Konfigurationen befinden sich in Dateien wie PROJECT_ROOT/buildout.cfgund PROJECT_ROOT/setup.cfg.

KT.
quelle
Danke für eine tolle Antwort! Sie haben viele Dinge für mich geklärt! Ich habe nur eine Frage: Können Eier verschachtelt werden?
Shookie
Nein, Sie können keine Eier im Sinne des Speicherns von .egg-Dateien in anderen .egg-Dateien "verschachteln" und hoffen, dass dies von großem Nutzen ist [es sei denn, Sie haben etwas wirklich Seltsames vor]. Sie können jedoch "virtuelle" Eier erstellen - leere Pakete, die keinen nützlichen Code enthalten, aber andere Pakete in ihren Abhängigkeitslisten auflisten. Auf diese Weise installiert ein Benutzer, wenn er versucht, ein solches Paket zu installieren, rekursiv viele abhängige Eier.
KT.
@KT können Sie etwas näher darauf eingehen, wie Sie mit generierten Daten umgehen? Wie unterscheiden Sie (im Code) insbesondere zwischen Entwicklung und Bereitstellung? Ich stelle mir vor, Sie haben eine base_data_locationVariable, aber wie stellen Sie sie richtig ein?
cmyr
1
Ich nehme an, Sie sprechen von "Laufzeitdaten" - etwas, das die Leute oft unter / var / packagename oder ~ / .packagename / var oder so weiter setzen. Meistens reichen diese Auswahlmöglichkeiten standardmäßig aus, damit Ihre Benutzer sie nicht ändern möchten. Wenn Sie zulassen möchten, dass dieses Verhalten angepasst wird, gibt es zahlreiche Optionen, und ich glaube nicht, dass es eine einzige bewährte Methode gibt. Typische Auswahlmöglichkeiten: a) ~ / .packagename / configfile, b) exportiere MY_PACKAGE_CONFIG = / path / to / configfile c) Befehlszeilenoptionen oder Funktionsparameter d) Kombination dieser.
KT.
Beachten Sie, dass es durchaus üblich ist, irgendwo eine Singleton-Konfigurationsklasse zu haben, die Ihre bevorzugte Konfigurationsladelogik für Sie verwaltet und den Benutzer möglicherweise sogar zur Laufzeit die Einstellungen ändern lässt. Im Allgemeinen denke ich jedoch, dass dies ein Thema ist, das eine separate Frage wert ist (die möglicherweise irgendwo zuvor hier gestellt wurde).
KT.
15

Nach meiner Erfahrung ist es nur eine Frage der Iteration. Platzieren Sie Ihre Daten und Ihren Code überall dort, wo Sie glauben, dass sie sich befinden. Die Chancen stehen gut, dass Sie sich sowieso irren. Aber sobald Sie eine bessere Vorstellung davon haben, wie sich die Dinge entwickeln werden, sind Sie in einer viel besseren Position, um solche Vermutungen anzustellen.

In Bezug auf Erweiterungsquellen haben wir ein Code-Verzeichnis unter Trunk, das ein Verzeichnis für Python und ein Verzeichnis für verschiedene andere Sprachen enthält. Persönlich bin ich eher geneigt, beim nächsten Mal einen Erweiterungscode in ein eigenes Repository zu stellen.

Nachdem dies gesagt ist, kehre ich zu meinem Ausgangspunkt zurück: Machen Sie keine zu große Sache daraus. Stellen Sie es an einen Ort, der für Sie zu funktionieren scheint. Wenn Sie etwas finden, das nicht funktioniert, kann (und sollte) es geändert werden.

Jason Baker
quelle
Ja. Ich versuche, "pythonisch" zu sein: explizit ist besser als implizit. Verzeichnis-Erben werden mehr gelesen / überprüft als geschrieben. Etc ..
eric
10

Nicht-Python-Daten werden am besten in Ihren Python-Modulen mithilfe der package_dataUnterstützung in setuptools gebündelt . Ich empfehle dringend, Namespace-Pakete zu verwenden, um gemeinsam genutzte Namespaces zu erstellen, die von mehreren Projekten verwendet werden können - ähnlich wie bei der Java-Konvention, Pakete einzufügen com.yourcompany.yourproject(und einen gemeinsam genutzten com.yourcompany.utilsNamespace zu haben ).

Beim erneuten Verzweigen und Zusammenführen werden Zusammenführungen auch durch Umbenennen verarbeitet, wenn Sie ein ausreichend gutes Versionsverwaltungssystem verwenden. Basar ist besonders gut darin.

Im Gegensatz zu einigen anderen Antworten hier bin ich +1 für ein srcVerzeichnis der obersten Ebene (mit docund testVerzeichnissen daneben). Spezifische Konventionen für Dokumentationsverzeichnisbäume variieren je nach Verwendung. Sphinx hat zum Beispiel seine eigenen Konventionen, die sein Schnellstart-Tool unterstützt.

Bitte nutzen Sie setuptools und pkg_resources. Dies erleichtert anderen Projekten das Verlassen auf bestimmte Versionen Ihres Codes erheblich (und das gleichzeitige Installieren mehrerer Versionen mit verschiedenen Nicht-Code-Dateien, wenn Sie diese verwenden package_data).

Charles Duffy
quelle