Grundgerüst fürPythonscript

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.
Antworten
dll-live
User
Beiträge: 35
Registriert: Dienstag 11. August 2020, 09:25
Wohnort: CH

Hallo zusammen

Was haltet ihr von folgendem Grundgerüst für ein Pythonscript.

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-

#Versionsliste
# V1 - Script erstellen

# Script weit gültige Konstanten setzen
MANUELLESTEUERUNG = False  # True / False

# "Offizielle" Module importieren
from sys import exit as s_exit
from os import environ
from time import sleep

# Systemparameter einlesen
if MANUELLESTEUERUNG:
    system_umgebung = "wuff"
    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
    print("!!! Achtung manuelle Systemsteuerung eingeschaltet!!!")
    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
    sleep(2)
else:
    system_umgebung = environ.get("SYSTEM_UMGEBUNG")
    if system_umgebung == None:
        s_exit("System Parameter nicht gesetzt! -  Script wird beendet")

# Eigene Module importieren:
from Modul_TMV import TMV

#  Funktionen definieren
def wert_anzeigen(wert, anzeige_ein = True):
    if system_umgebung != "Live" and anzeige_ein:
        print(wert)

# Mainroutine vom Script aufrufen
def main():
    wert_anzeigen("normales Programm")
    neues_Mail = TMV()
    # weiterer Code....

# Eintiegspunkt vom Script
if __name__ == "__main__":
    main()

Besten Dank für euere Kommentare und ggf hilfreiche Ergänzungen...
Gruss Dani
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Das einzige Grundgerüst, das man braucht, ist eine main-Funktion und die letzten beiden Zeilen in Deinem Code. Alles andere hat mit einem Grundgerüst nichts zu tun.
Alle Importe stehen ganz oben im Code, noch vor den Konstanten.
Es macht keinen Sinn exit in s_exit umzubenennen, das s ist ein kryptisches Anhängsel, die man vermeiden sollte.
Was ist der Sinn von MANUELLESTEUERUNG oder system_umgebung?
Was bedeutet der Wert "wuff" für system_umgebung?
Auf None vergleicht man per ›is‹.
Module werden wie Variablen und Funkionen komplett klein geschrieben und enthalten ebenso keine nichtssagende Präfixe (Modul_) oder kryptische Abkürzungen (TMV).
Das was Du mit ›wert_anzeigen‹ versuchst zu lösen, ist wahrscheinlich das, was man normalerweise mit dem Loggingmodul macht, also eine konfigurierbare Einstellung, wann was auf dem Bildschirm ausgegeben werden soll.

Code: Alles auswählen

import logging
logger = logging.getLogger(__name__)

def main():
    logging.basicConfig()
    logging.info("normales Programm")
    ...

if __name__ == "__main__":
    main()
Zwangsgestörter
User
Beiträge: 20
Registriert: Freitag 23. Oktober 2020, 19:00

Moin,

ich will auch mal meinen Senf dazugeben.

Kommentare: Die sind unnötig so wie du sie gemacht hast. Im Grunde doppelst du mit denen nur, was du eine Zeile später machst. Wenn du einen Kommentar machst, dann sollte da eher stehen, warum du eine Sache machst (sollte lieber aus dem Code klar werden, geht aber nicht immer) oder bei komplexen Algorithmen, wie der funktionert. Einfach nur zu schreiben, dass du gleich globale variablen deklarierst/initialisiert ist unnötig.

Deutsch: Ist jetzt eine persöhnlich Sache. Code sollte auf Englisch sein. Tatsache ist, dass man Code viel öfter liest als schreibt (Quelle trust me bro 😁). Beim Lesen von Code muss man mit verschiedenen Sprachen arbeiten. Zum einen die Programmiersprache, dann Englisch (weil meist die Keywords und Standardnamen dem Englischen sind) und dann noch in deinem Fall als dritte Sprache deutsch, und dadurch ein wildes Denglisch. Das macht das Lesen unnötig schwer.

Version: So was gehört in ein Softwareverwaltungssystem. Beim Lesen eines Quelltextes will man doch wissen, was die Methode/Klasse/Wasauchimmer macht. So eine Historie wird ziemlich schnell ziemlich groß und dann ist man erst mal am rumscrollen um den eigentlich code zu finden.

Bei meinen Codes steht die main() eigentlich immer ganz oben (nach den imports und nötigen deklarationen natürlich). So kommt man am schnellsten zu dem Punkt wo man einsteigen kann, weil da ja letztendlich alles passiert.

Ein letztes noch. Boolsche parameter sind im Grunde nicht gut, weil sie schlecht zu lesen sind. In deinem Beispiel kann der Aufruf so erfolgen: 'wert_anzeigen("hallo", False)'. was macht False noch mal? Vielleicht ein Newline abschalten? Du kannst in Python wenigstens erzwingen, dass man den Parameter benennen muss

Code: Alles auswählen

def wert_anzeigen(wert, *, anzeige_ein = True):
    ...
Jetzt kann man die Funktion nur noch mit dem Parameternamen aufrufen:

Code: Alles auswählen

wert_anzeigen(12345, anzeige_ein=False)
so, fertsch.

Wie immer ist alles nur meine persönlich Meinung. Letztendlich ist es wie immer: Frage 3 Programmierer und du erhälst 4 unterschiedliche Antworten.

Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Zwangsgestörter: Ich würde gegen `main()` ganz oben argumentieren, denn ganz unten findet man die ja auch genau so schnell und dort ist auch der Einstiegspunkt, das ``if __name__ == "__main__":`` das erst kommen kann/darf wenn alles andere definiert ist. Ich ordne so an, das auch bei der normalen Lesereihenfolge von oben nach unten möglichst alles was verwendet wird, vorher definiert wurde. Für die Laufzeit ist das ja zwingend in Python, also warum sollte man dann beim Quelltext davon abweichen, und nach welchen Kriterien.

Versionshistorie sehe ich grundsätzlich auch so, mit der Ausnahme von ”tatsächlichen Skripten”, also so Ein-Dateien-Dinger die zwar auch in einer Versionsverwaltung stecken können, die aber auch andere Programmierer oder Admins irgendwo auf einem Server vorfinden können, und keine Ahnung von oder keinen Zugriff auf die Versionsverwaltung haben.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
dll-live
User
Beiträge: 35
Registriert: Dienstag 11. August 2020, 09:25
Wohnort: CH

Hallo Zusammen

Besten Dank für euere Antworten.

Nachfolgend ein paar Erklärungen / Präzisierungen:
Alle Importe stehen ganz oben im Code, noch vor den Konstanten.
Das habe ich verstanden, jedoch frage ich mich,, wie ich in diesem Fall eigene Module importieren kann, welche in einem anderen Verzeichnis als dem Arbeitsverzeichnis liegen.Bis jetzt kenn ich nur den Weg. dass die"Path" Variable erweitert wird und so die Module dann importiert werden. Für Neues bin ich offen.

Code: Alles auswählen

from sys import exit as s_exit
mach ich, das ich das "System-Exit" verwende. den exit selber gibt es in python auch.

wert_anzeigen nutze ich dazu um wie der Name sagt - werte anzuzeigen. Das logging Modul ist mir bekannt, jedoch erfüllt das meine Wünsche nicht ganz (z.B.: einen Wert schnell (temporär) nicht anzuzeigen, während die anderen angezeigt werden sollten....
Was ist der Sinn von MANUELLESTEUERUNG oder system_umgebung?
Was bedeutet der Wert "wuff" für system_umgebung?
Das nutze ich als "Schalter" um zu entscheiden, welche "Wege" später genutzt werden... z.B.: Von wo Module importiert werden sollen, da die Pfade unterschiedlich sind.
Der Wert "wuff" ist ein Beispiel.
Auf None vergleicht man per ›is‹.
Danke für den Tipp, werde ich in Zukunft gerne umsetzen.
Kommentare
Besten Dank für euere Inputs.
Da aktuell noch kein "Softwareverwaltungssystem" eingesetzt / genutzt wird. steht das noch in Script. Mal schauen was die Zukunft hier bringt.... (Danke für den Hinweis)
in letztes noch. Boolsche parameter sind im Grunde nicht gut, weil sie schlecht zu lesen sind. In deinem Beispiel kann der Aufruf so erfolgen: 'wert_anzeigen("hallo", False)'. was macht False noch mal? Vielleicht ein Newline abschalten? Du kannst in Python wenigstens erzwingen, dass man den Parameter benennen muss
Danke für den Tipp, werde ich in Zukunft gerne umsetzen.

Gruss Dani
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

`exit` kommt in Programmen eh so gut wie nie vor; wenn Du aber wirklich sys.exit benutzen willst, dann rufst Du einfach sys.exit auf.
Auch eigene Module werden installiert, z.B. in einem eigenen virtuellen Environment. Notfalls, zum Testen, kann man auch die Environmentvariable PYTHONPATH entsprechend setzen, ganz Notfalls, indem man eine pth-Datei ablegt. sys.path wird innerhalb eines Programmes nicht verändert, vor allem nicht, wenn man dafür extra noch einen Schalter programmiert, der hardkodierte Pfade enthält.

Generell sollten keine fixen Pfade oder "Systemkonfigurationen" innerhalb des Programms stehen. Dann sind solche Schalter wie MANUELLESTEUERUNG oder system_umgebung auch nicht nötig.

Ich verstehe den Anwendungsfall "temporär schnell nicht anzeigen" nicht. Wenn man was gezielt anzeigen oder nicht-anzeigen will, dann benutzt man dafür das Logging-Modul und konfiguriert den entsprechenden Logger mit dem entsprechenden Loglevel.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@dll-live: Das man selbst in einem Modul den `sys.path` erweitert ist ungewöhnlich. Wenn man mehrere Module hat, steckt man die in ein Package. Sollte man eigene Bibliotheken/Packages haben, dann installiert man die, so dass man den Modulsuchpfad nicht erweitern muss, weil die ganz normal importiert werden können.

`exit()` gibt es nicht wirklich. Das muss es nicht unter allen Umständen geben, und es ist für die Verwendung im interaktiven Interpreter und sollte in Programmen nicht verwendet werden. Also kann man es, falls es definiert ist, in Programmen auch mit einem Import von `sys.exit()` verdecken und muss da keinen kryptischen Präfix dran hängen.

Wenn man etwas temporär nicht anzeigen lassen will, dann kann man das `print()` dafür auch einfach temporär auskommentieren. Eine Funktion mit einem boole'schen Argument ist da nicht wirklich einfacher und verständlicher.

Und auch globale Schalter die dann überall im Programm verwendet werden um zu entscheiden was denn letztlich wo gemacht wird, sind sehr unübersichtlich. Und machen es zudem unnötig schwer bis nahezu unmöglich vernünftige Tests zu schreiben. Man muss dann ja potentiell jede Funktion mit allen Schalterkombinationen durchtesten. Alleine mit den Werten die man im Beispiel sieht, wären das ja schon 6 mögliche Kombinationen von den beiden Schaltern, wobei `system_umgebung` keinen Wertebereich erkennen lässt, es also potentiell noch mehr Werte geben könnte, die diese (hoffentlich) Konstante annimmt.

Von wo Module importiert werden, wird normalerweise nicht durch Konstanten oder Variablen im Programm gesteuert. Ausnahmen bestätigen die Regel, beispielsweise wenn man ein Plugin-System hat und deshalb keine Festen Namen für Module kennt wenn man den Code schreibt.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
dll-live
User
Beiträge: 35
Registriert: Dienstag 11. August 2020, 09:25
Wohnort: CH

Hallo

@Sirius3: Besten Dank für deine schnellen und ausführlichen Antworten.

Code: Alles auswählen

from sys import exit as s_exit
Da ich mal gelesen habe, und entsprechend versuche umzusetzen importiere ich, wenn möglich, nur das benötigte von einem Modul und dann ergeben sich so Situationen wie bei system.exit und exit...
Gibt es für diesen Bereich auch schönerer / einfacher / elegantere Lösungen?

Code: Alles auswählen

from sys import path
path.insert(0, support_pfad)
Die Variable "support_pfad" ist natürlich entsprechend befüllt
Wie werden den solche Sachen "schöner" / "eleganter" gelöst?
Ich verstehe den Anwendungsfall "temporär schnell nicht anzeigen" nicht. Wenn man was gezielt anzeigen oder nicht-anzeigen will, dann benutzt man dafür das Logging-Modul und konfiguriert den entsprechenden Logger mit dem entsprechenden Loglevel.
Diesen Bereich können wir so stehen lassen. Hier passt für mich die Lösung die ich habe und ich kenne das Logging Modul.

Freundliche Grüsse
Dani
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das man am sys.path nicht spielt, stimmt nicht. Das ist durchaus legitim in Eintrittspunkten zu einem Projekt. Klar kann man auch setup.py und entry_points definieren, aber das setzt mehr voraus, als für kleinere oder von uninittierten genutzten Projekten gegeben ist. Wenn ich jedem unserer 70 C++-Entwickler die Wunder des Python Paketmanagements beibiegen muss, statt sys.path verantwortungsvoll zu manipulieren, nutze ich letzteres.
dll-live
User
Beiträge: 35
Registriert: Dienstag 11. August 2020, 09:25
Wohnort: CH

Hallo

@ __blackjack__ : Auch dir besten Dank für schnellen und ausführlichen Antworten, vorhin hatte ich deine Antwort übersehen. - Das Auge war wieder einmal langsamer als mein Hirn / Finger beim Antwort schreiben so nebenbei in einem Weiterbildungskurs....
@ __deets__ :Auch dir besten Dank für schnellen und ausführlichen Antworten,

Generell an alle (sirius3, _blackjack__, __deets__, etc.) welche sich bemühen und mir Tipps geben. Danke dafür!
@dll-live: Das man selbst in einem Modul den `sys.path` erweitert ist ungewöhnlich. Wenn man mehrere Module hat, steckt man die in ein Package. Sollte man eigene Bibliotheken/Packages haben, dann installiert man die, so dass man den Modulsuchpfad nicht erweitern muss, weil die ganz normal importiert werden können.
Besten Dank für den Hinweis, dies werde ich mir mal anschauen. - Vorläufig bin ich jedoch noch nicht so weit....

Bezüglich exit() danke für die Info.

Bezüglich "den Schalter" - diese benutze ich als Unterscheidung für die Test / Prod Umgebung - wenn man es denn so nennen will. (ist immer noch Heimgebrauch - aber zum Testen läuft's auf dem Laptop danach auf dem Server....).
Die Funktionen in diesen "Modul(en)" sind getestet. Sprich haben auf beiden Systemumgebungen die gleichen Funktionen... 8) 8) 8)
.....diese (hoffentlich) Konstante .......
Ja, ist es sonst wird es ja nicht in GROSSBUCHSTABEN geschrieben.. (manchmal lern auch ich Dinge schnell...... 8) :lol: )

Gruss
Dani
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@dll-live: Mit getestet sind automatisierte Tests gemeint, nicht das die einmal getestet worden sind. Und die Test- und Produktiv-Umgebung sollte den gleichen Code ohne Weichen benutzen, denn sonst testet man in der Testumgebung ja gar nicht den tatsächlichen Code sondern anderen, der nur in der Testumgebung läuft.

`system_umgebung` ist im ursprünglichen Code ja klein geschrieben, deshalb das „(hoffentlich)“, denn das sollte ja eigentlich GROSS geschrieben werden, als Konstante.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
dll-live
User
Beiträge: 35
Registriert: Dienstag 11. August 2020, 09:25
Wohnort: CH

Hallo
@__blackjack__: Das habe ich verstanden. Soweit das automatische Tests gefahren (oder benötigt / gefordert) werden, bin ich noch meilenweit entfernt.
Bis jetzt sind machen die "Schalter" in meinen Scripten auch nur "unwesentliche" Änderungen am Code (z.B.: Einstellung das der korrekte DB-Name genommen wird, oder gibt mir retour auf welchem System eine Aufgabe ausgeführt wird, etwas festgestellt wurde... etc.)

Im angepassten "Grundgerüst" sind die Anpassungen aus dieser Diskussion enthalten.

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-

#Versionsliste
# V1 - Script erstellen

MANUELLE_STEUERUNG = False  
from sys import exit as sys_exit
from os import environ
from time import sleep

if MANUELLE_STEUERUNG:
    SYSTEM_UMGEBUNG = "Test"
    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
    print("!!! Achtung manuelle Systemsteuerung eingeschaltet!!!")
    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
    sleep(2)
else:
    SYSTEM_UMGEBUNG = environ.get("SYSTEM_UMGEBUNG")
    if SYSTEM_UMGEBUNG is None:
        sys_exit("System weit gültige Parameter nicht gesetzt! -  Script wird beendet")


def wert_anzeigen(wert, *, anzeige_ein = True):
    if SYSTEM_UMGEBUNG != "Live" and anzeige_ein:
        print(wert)


def main():
    wert_anzeigen("normales Programm")
    wert_anzeigen("Test", anzeige_ein=False)
    # weiterer Code....


if __name__ == "__main__":
    main()
Gruss
Dani
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@dll-live: Das ``from sys import exit as sys_exit`` sieht unnötig kompliziert aus. Warum nicht einfach ``import sys`` wenn Du die Herkunft unbedingt beim Aufruf sichtbar machen möchtest? Denn `sys_exit(…)` und `sys.exit(…)` machen da ja nicht wirklich einen Unterschied beim Aufruf.

Was Unit-Tests angeht, sieht mir das durchaus schon umfangreich genug aus, aber was wichtiger ist: Wenn Du den Code nicht jetzt schon testbar schreibst, wirst Du das nie tun, weil sich dann immer mehr Sachen anhäufen die schlecht testbar sind, und die man dann erst noch mal umschreiben müsste, und so die Hürde immer grösser wird mit der Zeit. Selbst wenn man keine Tests schreibt, ist testbar geschriebener Code in der Regel besser; ohne globalen Zustand, mit Funktionen die nicht zu viel verschiedene Dinge tun, und besseren Schnittstellen.

Die Konstante `MANUELLE_STEUERUNG` sollte nach den Importen definiert werden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten