Code-Review zur Thematik "Konstanten"

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Der Vollständigkeit und Korrektheit halber möchte ich folgendes erwähnen (auch wenn dies möglicherweise eher unüblich ist):

Mein weiter vorne geposteter Code enthält einen Fehler, da ich dort log_path.parent.mkdir(..) aufgerufe. Das ".parent" ist jedoch falsch, wenn man möchte, dass der Unterordner "logs" automatisch angelegt wird, falls es zu Fehlern kommt.

So wäre es aus meiner Sicht richtig:

Code: Alles auswählen

def initialize_logging():
    logs_path = BASE_PATH / "logs"
    logs_path.mkdir(parents=True, exist_ok=True)
    log_file_path = logs_path / "Sprueche_ausgemustert.txt"
    logging.basicConfig(filename=str(log_file_path), level=logging.ERROR)
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Hi zusammen,

ich bin gerade dabei, mit spaCy herum zu experimentieren. Und meine Pipeline ist voller Ideen, was man da mal so machen könnte. D. h. je nach dem wie viel Zeit ich erübrigen kann, wird das Spielzeug-Projekt evtl. weiter wachsen. Außerdem möchte ich anhand dieses Beispiels auch mal Unit-Tests, Git-Integration in PyCharm etc. ausprobieren.

Daher überlege ich (schon) jetzt, wie man die Dateien künftig strukturieren könnte.
In Delphi ist es leider so, dass spätere Umstrukturierungen zu erheblichen Mehraufwänden führen. Daher ist es dort gut, möglichst frühzeitig alles in sinnvolle Strukturen zu bringen. Evtl. ist dies in Python nicht notwendig/nicht sinnvoll, da die Werkzeuge für solche Umbauten besser sind. (Soweit ich das erkennen kann, scheint das auch so zu sein)

So nun zu meiner Frage hierzu:
PyCharm legt bei neuen Projekten eine main.py-Datei an.
Ähnliches kenne ich auch in Delphi. Dort ist es (für Desktopanwendungen) üblich, dass die main.pas nur sowas wie Application.Run und ggf. einige wenige Konfigurationen etc. enthält, die _vor_ oder _nach_ Application.Run greifen müssen. Sprich dort steht nur Code, der nur beim Einstiegspunkt stehen darf.

Ist das in Python ähnlich?
In PEP-8 /google habe ich dazu jetzt nichts gefunden.

Sollte jedes größere Projekt eine main.py haben, um den Einstiegspunkt kenntlich zu machen?
Und wenn ja, was sollte/darf (bei größeren Projekten) drin stehen?

(Ich weiß, dass das keine "schönen" Quellcode-Fragen sind und vielleicht sind sie sogar für Python total schwachsinnig. Aber mich interessiert hier Eure Erfahrung/Meinung, Mir geht's drum, nicht stumpfsinnig Delphi-Vorgehensweisen zu übernehmen, weil "ich das schon immer so gemacht habe")

LG
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Buchfink hat geschrieben: Montag 27. Dezember 2021, 13:53 Daher überlege ich (schon) jetzt, wie man die Dateien künftig strukturieren könnte.
In einer flachen Ordnerstruktur. Ich denke du meinst "Module" statt Dateien? Innerhalb eines Python-Pakets stellen Module einen gemeinsamen Namensraum dar.
Komponenten, die eine starke Abhängigkeit haben, kommen in ein Modul. Ansonsten gilt alles was einem sonst noch zum Thema Namensraum einfällt. (Falls einem nichts einfällt, sollte man sich auch nicht künstlich den Kopf zerbrechen :) )
Manchmal liest man, dass ein Modul nur so und so viele Zeilen umfassen soll. Das ist meiner Meinung nach totaler Quatsch. Ich würde ein Modul niemals aufsplitten nur weil es zu viele Zeilen enthält.
Die Struktur ist flexibel und jederzeit änderbar.
Zum Thema Paketstruktur kann man aber auch viel im Netz finden.
Sollte jedes größere Projekt eine main.py haben, um den Einstiegspunkt kenntlich zu machen?
Es gibt in Python keine Vorgabe, dass es immer eine "main.py" geben muss. Falls du eine Library schreibst, die von anderen Programmen konsumiert wird, besteht gar kein Bedarf dafür. Es gibt ja keinen Einstiegspunkt.
Ich habe gerade mal einen schnellen Blick in den Source-Code von pandas und numpy geworfen und keine "main.py" gefunden. Macht ja auch Sinn, denn es sind keine eigenständig ausführbaren Pakete.

Wenn du ein eigenständig ausführbares Paket erstellst, soll es ja sicher als solches verpackt werden und über pip installierbar sein. Bei einer Konsolenanwendung wird man dann den Paketnamen mit entsprechenden Argumenten aufrufen und gar nicht merken welches Modul im Hintergrund als Einstieg dient. Von daher besteht also auch kann Zwang "main.py" zu verwenden. Das einzige Argument für "main.py" sehe ich in der Lesbarkeit des Codes und im besseren Verständnis, wie das Paket aufgebaut ist. Denn wenn man ein größeres Paket verstehen will, hilft es schon einen gewissen Anfangspunkt zu haben.
Manchmal wird auch die __init__.py an oberster Ebene des Pakets für den initialen setup genutzt.

Wenn man mal "black" als Beispiel für eine Konsolenapplikation nimmt, passt das auch gut ins Bild, denn black hat eine main.py (eigentlich __main__.py). Die ist dem Benutzer aber verborgen, denn black wird im Scripts-Ordner bei der Installation als Executable abgelegt.

Lange Rede kurzer Sinn: main.py wenn überhaupt, dann nur für eigenständige Applikationen
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@roger_b

vielen Dank für Deine hilfreiche Rückmeldung!
Ich denke du meinst "Module" statt Dateien?
ja, sofern ich den Begriff "Module" in Python richtig verstanden habe.

Um meine Frage etwas zu präzisieren:
Wie/wo würde man Unit-Tests sinnvollerweise ablegen?
Meine Idee wäre, eine separaten Unterordner und dann die Unit-Testdateien so zu benennen, wie das zu testenden Module:
Z. B. "
beispiel.py" und "UnitTests/test_beispiel.py".

(Hier habe ich aus Delphi leider _gar_ keine Erfahrung, da das dort nicht besonders gut geht)

Danke für den Tipp mit "Paketstrutkur". Darüber bin ich nun auf diesen Forenbeitrag gestoßen, den ich mir nochmal genauer durchlesen muss.
viewtopic.php?t=36228

Am Rande: im Moment sähe es so aus bei mir

Projekt "TKocher" --> Der Name für ein Wortspielgenerator muss natürlich selbst ein Wortspiel sein ;-)

Code: Alles auswählen

TKocher
- Daten
  - Sprueche.txt
  - logs
     - Sprueche_ausgemustert.txt
- main.py
Manchmal liest man, dass ein Modul nur so und so viele Zeilen umfassen soll. Das ist meiner Meinung nach totaler Quatsch. Ich würde ein Modul niemals aufsplitten nur weil es zu viele Zeilen enthält.
In Delphi gruppiere ich (Units = Dateien) so, dass die (technischen) Abhängigkeiten minimal, aber Klassen/Proceduren, die semantisch zusammenhängen in einer Unit stehen. (Bei zirkulären Referenzen muss man splitten. Es gibt diesbezüglich ein paar "ungeschriebene Gesetze". Wenn man sich dran hält, funktioniert das gut.)
So wie ich das sehe, kann ich das in Python ähnlich machen.
Wobei ich nochmal recherchieren muss, was denn genau ein Paket in Python bedeutet.

Die Struktur ist flexibel und jederzeit änderbar.
Gut zu wissen! D. h. ich fange mal mit wenig Struktur an und baue später um, falls es notwendig wird.
Wenn du ein eigenständig ausführbares Paket erstellst, soll es ja sicher als solches verpackt werden und über pip installierbar sein.
gefühlt bin ich davon noch meilenweit entfernt. :) Ich fange erst mal "klein" an und beschränke mich auf Applikationen (bzw. deren Backend*).
Lange Rede kurzer Sinn: main.py wenn überhaupt, dann nur für eigenständige Applikationen
Danke. Das werde ich dann auch mal so machen. Es passt auch zu dem, was ich von Delphi bereits kenne.


*) nach meiner persönlichen Erfahrung ist es besser, erst mal das Backend zu bauen, und dann ein Frontend dran zu kleben
Benutzeravatar
__blackjack__
User
Beiträge: 14028
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich starte in der Regel mit *einem* Modul, jetzt unabhängig davon ob es ein Programm oder eine Bibliothek sein soll. Wenn das zu lang wird überlege ich wie man es sinnvoll auf mehrere Module aufteilen kann. Dabei ist immer der erste Schritt bei mir das Modul in `__init__.py` umzubenenen, und den alten Modulnamen als Verzeichnis anzulegen, und das ursprüngliche Modul dort hinein zu verschieben. Damit ist das Modul zum Package geworden, das beim Importieren erst einmal den gleichen Inhalt hat wie vorher. Die `__init__.py` ist wichtig! Das macht das ganze zum Package. Ohne die `__init__.py` hat man nur ein Namespace-Package — mit den entsprechenden Folgen.

Falls es ein Programm war oder eine Bibliothek die auch eine Hauptfunktion hat, kommt da noch eine `__main__.py` rein, die der Einstiegspunkt ist wenn man das Package als Modul ausführt. Achtung! Das ist nicht nur irgendeine Konvention, sondern dieser Dateiname ist notwendig wenn man das Package selbst als Programm ausführen können möchte:

Code: Alles auswählen

$ python3 -m tmp
/usr/bin/python3: No module named tmp.__main__; 'tmp' is a package and cannot be directly executed
Manchmal macht es auch bei Bibliotheken Sinn die als Programm ausführen zu können. Entweder weil man eine einfache kleine Hauptfunktion basteln kann, die etwas sinnvolles macht, oder um das Modul zu demonstrieren. Das `rich`-Modul kann man beispielsweise als Programm ausführen und bekommt eine Übersicht über viele der Möglichkeiten wie Ausgaben damit aussehen können. Das Pillow-Package (`PIL`) gibt einen Statusbericht aus welche nativen Bibliotheken es gefunden/geladen hat und welche Dateiformate es unterstützt.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
Dabei ist immer der erste Schritt bei mir das Modul in `__init__.py` umzubenenen, und den alten Modulnamen als Verzeichnis anzulegen, und das ursprüngliche Modul dort hinein zu verschieben. Damit ist das Modul zum Package geworden,
Danke für den Hinweis im Hinblick auf die Vorgehensweise. Denn die Befürchtung Dinge nicht mehr gut ändern zu können, ist im Moment auch ein wenig meine "Bremse im Hirn".

Ich hab auch den Beitrag (viewtopic.php?t=36228) nochmals genauer gelesen und insbesondere was die "Unterordner" angeht, ist der "Delphi-Weg" wohl eher der "Holzweg" :)
(In Delphi sehe ich häufiger in Projekten Trennungen in Unterordner wie "Backend" und "Frontend". Das macht dort durchaus Sinn, wobei man sich da auch drüber streiten kann. Unter Python scheint das aber keine besonders gute Idee zu sein.)
Das `rich`-Modul kann man beispielsweise als Programm ausführen und bekommt eine Übersicht über viele der Möglichkeiten wie Ausgaben damit aussehen können
Gut zu wissen.
Das wäre in Delphi nach meinem Kenntnisstand unüblich. Zumindest ist mir derartiges dort noch nicht begegnet. (Muss aber nichts heißen, denn ich kenne da auch nicht alles :))
Antworten