Python 'Benzingespräche'

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.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@ulipy,

ich verliere langsam den Überblick aber basierend auf dem was ich mitgelesen habe:

Es besteht kein Grund dafür eine Klasse anzulegen. Meiner Meinung nach ist ein separates Modul, welches sich nur im diese Funktionalität kümmert geeignet. Darin liegen die nötigen Funktionen und die Konstante mit den Voreinstellungen.
Bei jedem Programstart wird geprüft, ob die Datei mit den Benutzereinstellungen vorhanden ist. Wenn nicht, wird er danach gefragt und die Datei wird neu erzeugt.

Aber da du aus irgendeinem Grund das gesamte Programm in ein Modul schreiben willst müsste das alles darein. Das wird schnell unübersichtlich. Eigentlich sollte der Code thematisch in Module unterteilt werden.
Zur Distribution kann man ja ein Paket erstellen. Dann hat man eine einzige Datei zum Verteilen.

Zu deinem Code (Meine Meinung):
Das Attribut name wird nicht verwendet, kann also weg.
Der Kommentar im Methoden-, Funktionen-, Modul- oder Klassenkopf heißt docstring und sollte in dreifache Anführungszeichen gesetzt werden, nicht das Kommentar Symbol #.
Datei-Operationen immer im with-Kontext
Dateinamen in einem Klassenattribut oder einer Konstanten auf Modulebene speichern um sie allen Instanzen zum Lesen zur Verfügung zu stellen.
Pfade als Path-Objekt anlegen.
An die Update-Methode würde ich einen fertigen Dictionary übergeben, der wird mit dem aus der Datei geladenen Dictionary auf Gleichheit der Schlüssen geprüft und dann in die Datei geschrieben.
quit() nicht mitten im Script aufrufen, wenn überhaupt.
Statt print("Error") Exception handling verwenden.
Keine Leerzeichen in Dateinamen verwenden.
Encoding beim Lesen und Schreiben von Textdateien angeben.
Aussagekräftige Namen verwenden.
Sirius3
User
Beiträge: 18264
Registriert: Sonntag 21. Oktober 2012, 17:20

@rogerb: ab wann etwas unübersichtlich wird, ist eine fließende Grenze. Es spricht erst einmal nichts generelles dagegen, alles in einer Datei zu behalten.
Ich sehe im ganzen Code keinen Kommentar, der sich als Docstring eigenen würde.

@ulipy: tmp ist selten ein sinnvoller Variablenname, und garantiert nie ein sinnvolles Attribut. Zumal Du dieses Attribut auch nirgends als solches verwendest, sondern nur immer als lokale Variable.
MyClass ist ein sehr schlechter Klassenname, warum nicht `Settings`?
Welchen Zweck hat der `name`?
In Python steckt man niemals zwei Anweisungen in eine Zeile, auch wenn es ; gibt, benutzt man ihn nicht.
Die json-Datei wird bei jedem Programmstart neu erstellt. Somit ist sie eigentlich überflüssig.
Eine Konfigurationsdatei liegt üblicherweise irgendwo zentral und nicht im aktuellen Arbeitsverzeichnis.

Ich habe immer noch nicht verstanden, wie Du diese Daten nutzen willst. Warum werden die im Programm geändert? Was steht da konkret drin? Settings werden normalerweise von außen verändert. Der einzige Grund, warum DEFAULT-Werte im Programm stehen ist, dass man beim ersten mal eine solche Datei anlegen kann, falls sie noch nicht existiert.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Sirius3 hat geschrieben: Freitag 3. Dezember 2021, 08:55 Ich sehe im ganzen Code keinen Kommentar, der sich als Docstring eigenen würde.
Das ist richtig. Für die __init__() Methode passt das nicht. Ich würde aber dennoch bei einem Docstring als Erklärung für die Klasse bleiben und entsprechend verschieben.
Bzgl. der Aufteilung, würde ich versuchen soweit wie möglich die Aufteilung vorherzusehen und auch von Anfang an kleine Code-Abschnitte schon abtrennen. Das hier ist für mich eine separate Funktionalität die nichts mit der Hauptfunktionalität zu tun hat.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

@ulipy: Ich stelle mal den Sinn der Klasse in dieser Variante in Frage. Denn auf den ersten Blick scheint es so, als würdest du die Funktionalität eines dicts nachbauen, erweitert um das Dumpen. Da dads Dumpen in diesem Fall eher kontraproduktiv ist, weil die Datei immer wieder überschrieben wird, könnte man das ganze auch als dict abbilden und das dann dumpen.

Spannend wird das sowieso erst, wenn du es tatsächlich verwendest. Denn etwas in eine Klasse zu kapseln bewahrt einen ja keineswegs davor, die Intanzierung dann eine globale Variable zu binden.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

generell zum Verständnis:
  • es geht in diesem Entwicklungsschritt einzig um die Frage: Klasse oder nicht Klasse?
  • der aktuelle Status des codes zeigt mangels sicherer Terminologie (noch auf extrem umständliche Weise) lediglich die geplante Verwendung der Klasse auf
  • d. h., von allen Bäumen, welche die Sicht auf den Wald verstellen, kann ohne "Schaden" abstrahiert werden
  • auf irgend ein zum script externes Element möchte ich dabei ganz klar verzichten - die Begründung dafür führt jedoch von dieser Sache ab. Falls dies aus py-sprachbedingten Gründen nicht "machbar" wäre, wäre die Verwendung von Python "falsch"
-------------------------------------------------------------------------------------------

@rogerb
Bei jedem Programstart wird geprüft, ob die Datei mit den Benutzereinstellungen vorhanden ist. Wenn nicht, wird er danach gefragt und die Datei wird neu erzeugt.
- genau so ist das geplant

@Sirius3
In Python steckt man niemals zwei Anweisungen in eine Zeile, auch wenn es ; gibt, benutzt man ihn nicht.
Die json-Datei wird bei jedem Programmstart neu erstellt. Somit ist sie eigentlich überflüssig.
- ja, das ist eine der Krücken, die derzeit im aktuellen Code-Schnippsel steht - das kann dort "niemals nicht" verbleiben...
Dieses aktuell nachrangige Problem ist einer der hzu abstrahierenden "Bäume" und führt zu einem nachfolgenden Punkt - spare den vor einer Entscheidung erstmal aus

- ja, ich hatte versehentlich das vorrangige Lesen der json ausgelassen...

Reichen denn diese Angaben zu einer Vorentscheidung aus oder fehlt noch etwas Wichtiges dazu?
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@ulipy,

ich glaube das wurde dir schonmal gesagt, aber es scheint du möchtest es auf deine Weise machen und von allen anderen hier irgendwie die Bestätigung bekommen das das Okay ist...
Ich denke ein wichtiger Schritt beim Programmieren lernen ist es auch eigene Fehler zu machen und daraus zu lernen.
Ich glaube auch nicht alles nur weil jemand sagt: "Das ist so". Das Ergebnis eines solchen Verhaltens ist, dass man entweder tatsächlich eine bessere Lösung findet oder sich eines besseren belehren lassen muss.
Beides sind positive Ergebnisse.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

@rogerb
damit wir nicht aneinander vorbeireden: auf welchen Punkt beziehst du dich genau?
Sirius3
User
Beiträge: 18264
Registriert: Sonntag 21. Oktober 2012, 17:20

@ulipy: um es ganz klar zu sagen: in diesem Entwicklungsschritt keine Klasse!
Solange Du hier nicht erklären kannst oder willst, was Deine eigentliche Anwendung ist, wird Dir hier auch niemand etwas anderes sagen können. Klassen sind kein Selbstzweck, sondern nur dann sinnvoll, wenn die Komplexität eine Klasse zu definieren an anderer Stelle mehr Komplexität einsparen läßt. An zwei Dummy-Anwendungszeilen Code läßt sich das nicht zeigen.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

@Sirius
Danke! Deine klare Aussage hilft hier weiter - der Zweck des kleinen Programms und die Verwendung von Objekten im Programm wird auf jeden Fall hier noch dargestellt.

Aus privatem Bedarf heraus ist - wie so oft - die Absicht zu einem öffentlich (manuell oder als cron-job ) verwendbaren Werkzeug für die Erstellung von Versionskopien täglicher Arbeit am Rechner geworden - GPL "natürlich".

Diese Sache ist mit Sicherheit nicht neu - es geht um die Erstellung einer passgenauen und dennoch flexiblen Variante für eine sehr begrenzte Reihe möglicher Nutzer.

Da ich persönlich "das Rad" ganz gerne auch mal neu erfinde, kann sich das ggfs. auch als Übung in Python darstellen..
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

ulipy hat geschrieben: Freitag 3. Dezember 2021, 11:34 @rogerb
damit wir nicht aneinander vorbeireden: auf welchen Punkt beziehst du dich genau?
ulipy hat geschrieben: Freitag 3. Dezember 2021, 12:32 Da ich persönlich das Rad ganz gerne auch mal neu erfinde, kann sich das ggfs. auch als Übung in Python darstellen..
Genau das meinte ich.
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

ulipy hat geschrieben: Mittwoch 1. Dezember 2021, 17:05 Du gibst nun dem user beim initialen Start die Möglichkeit, keinen oder auch jeden dieser Werte an seine Bedürfnisse anzupassen (kein GUI, Kommandozeile).
Dann schreibst du das fest auf Platte.
Das, was danach von Platte gelesen wird, sind per Definition user settings.
Aus dem script wird das danach "nie mehr" benötigt oder gelesen.
Es gibt viele Wege zum Ziel. Ich gebe Dir mal ein Beispiel, wie es umgesetzt werden könnte:

Code: Alles auswählen

from configparser import ConfigParser
from pathlib import Path


SETTINGS_FILE = Path().resolve().parent / "configuration.ini"
SETTINGS_FILE_ENCODING = "utf-8"


def get_settings(settings_file):
    """
    Takes a Path-instance as settings_file and
    returns a dictionary with the settings.
    """
    defaults = {
        'del_orphaned': False,
        'demo_only': False,
        'hide_versions': False,
        'symlinks_dirs': False,
        'symlinks_files': False,  
    }
    config = ConfigParser(defaults=defaults)
    if settings_file.exists():
        with open(settings_file, "r", encoding=SETTINGS_FILE_ENCODING) as fobj:
            config.read_file(fobj)
            defaults = dict(config.items("DEFAULT"))
    else:
        with open(settings_file, "w", encoding=SETTINGS_FILE_ENCODING) as fobj:
            config.write(fobj)
    return defaults


settings = get_settings(SETTINGS_FILE)
print(settings)
Du hast hier eine Funktion "get_settings" die Dir die Settings als dictionary zurück gibt. Die Settings werden dabei aus der Datei "configuration.ini" gelesen, die sich im gleichen Verzeichnis wie Dein Skript befinden muß. Existiert diese Datei nicht, dann wird sie angelegt und die default-Werte werden verwendet. Existiert die Datei, dann überschreiben dortige Inhalte die default-Werte.

Das lässt sich alles auch noch schöner und umfangreicher machen (z.B. verschiedene Verzeichnisse, die der Reihe nach für eine .ini Datei durchsucht werden etc.) aber damit kannst Du schon mal ein wenig herumspielen.

Sicherlich erfordert diese Möglichkeit nun eine optionale(!) settings-Datei, aber das ist durchaus sinnvoll.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

@alle
Zum Verständnis:
Es geht nicht darum, irgendetwas stur zu verfolgen.

Aus den meisten Beiträgen war m. E. herauszulesen, dass die Informationen des Für und Wider der Verwendung einer Klasse im vorliegenden Fall nicht ausreichend sind.

Dies ist verstanden und hat auf meiner Seite dazu geführt, diese Verwendung zunächst als Option weiter zu entwickeln. Später erst wird man sehen können, was genau an dieser Stelle das "Richtige" ist.

@kbr
was für mich hier nicht klar ist:

Code: Alles auswählen

defaults = {
        'del_orphaned': False,
        'demo_only': False,
        .
        .
"defaults" ist eine Konstante, allerdings "nur" innerhalb einer klasse verwendet.
Würde "defaults" dann tatsächlich nicht wie "DEFAULTS" geschrieben?
Das nur der Vollständigkeit halber.


@Sirius3
sag bitte zur Linderung meiner Schmerzen :D , ob dir wenigstens Zweifel gekommen waren in der Interpretation dieser Platzhalter-Namen?
...
@ulipy: tmp ist selten ein sinnvoller Variablenname, und ...
...
MyClass ist ein sehr schlechter Klassenname, warum..
...
Hier zeigt sich gut, wie schwierig sich manchmal Kommunikation über ein so eingeschränktes Medium gestaltet...

Zur Hilfe bei der rein abstrakten Konstruktion einer Klasse habe ich hier viewtopic.php?f=1&t=53566 eine separate Frage gestellt
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

@ulipy:kleingeschrieben ist korrekt. Alternativ ließen sich die Werte auch modul-global zunächst an großgeschriebene Bezeichner binden. Lieber aber wäre es mir, wenn Du Dich mit der Funktion und deren Rückgabewert befasst.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

Danke @kbr, ist auf jeden Fall angekommen! Es geht halt nicht alles auf einmal..

Was mir an eine Klasse hier unter anderem sehr gefällt ist z. b. die Möglichkeit, die "versehentliche" Erzeugung eines Attributs durch Verwendung von __slots__ zu verhindern und die klare, einfache Abfrage eines der initialisierten Attribute.

Python lässt dem Programmierer manchmal erschreckend viel Freiheit..
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

ulipy hat geschrieben: Freitag 3. Dezember 2021, 22:40 Python lässt dem Programmierer manchmal erschreckend viel Freiheit..
Genau, viel zu viel Freiheit! Und wenn man nicht bei rot über die Straße zu gehen möchte, muss man seine Füße festnageln.

Anders ausgedrückt: Statt dir Mechanismen zu suchen, die irgendwas verhindern - genügt es nicht, es einfach nicht zu tun? Also statt __slots__ dafür zu missbrauchen, Attribute zu verhindern, sie einfach nicht anzulegen? Der einfache Weg ist in Python immer der richtige, außer in sehr wenigen, sehr speziellen Fällen, bei denen der Programmierer idR. genau weiß, warum er vom einfachen Weg abweicht. Was du dagegen tust, ist Probleme für Lösungen zu suchen.
In specifications, Murphy's Law supersedes Ohm's.
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

@pillmuncher
pillmuncher hat geschrieben: Freitag 3. Dezember 2021, 23:19
ulipy hat geschrieben: Freitag 3. Dezember 2021, 22:40 Python lässt dem Programmierer manchmal erschreckend viel Freiheit..
Genau, viel zu viel Freiheit! Und wenn man nicht bei rot über die Straße zu gehen möchte, muss man seine Füße festnageln.
..
.
Das kann schon so ausssehen - ich suche als Neuling zur Zeit hauptsächlich "trittfesten Boden".

Dazu gehört natürlich das Ausloten von Sprachelementen, ob nun restriktive oder freiere, kreativere. Ohne Nachdenken leicht befolgbare py-sprachlich konkrete Richtlinien habe ich noch kaum.

Eine geänderte Klassenkonstruktion unter Verwendung von __slots__ stelle ich jetzt ins Thema viewtopic.php?f=1&t=53566 rein
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

Hallo zusammen,
nach dem crash-Einstieg, welcher notwendig chaotisch verlaufen ist und durch eure vielen Beiträge ermöglicht wurde,
habe ich nun eine Vor-Version eines Funktionsobjekts erstellt und versucht, innerhalb eines Levels 1 (von 3) wenigstens Konventionen und Sprachelemente einigermaßen "sauber" zu verwenden.

Die semantisch bedingten Logik-Weichen darin sind hier nicht entscheidend - ein von @__blackjack__ erwähnter Unittest wäre dazu natürlich Gold wert.. - dieser steht derzeit jedoch wie gesagt nicht an.

Wäre sehr dankbar, wenn der Code auf die erkennbaren Python Schwächen abgeklopft werden könnte:

Code: Alles auswählen

import os
import json

# !access settings via this function only please!
def rw_settings(*args):
    """
    Read or write settings
    If no settings-file is found, use defaults
    
    Access / args samples:
    Read:
      rw_settings('read_default_data')
      rw_settings('_symlinks_dirs')
    
    Write:
      rw_settings('_symlinks_dirs', 'True')
    """
    
    if 0 < len(args) > 2:
        # no of args: 1 or 2 only!
        print('!ERROR!   !ERROR!   !ERROR!')
        print('expected 1 or 2 args only, received:', len(args))
        quit()

    # edit data_path for release !!!
    data_path = '.\\dummy_f_dfg_ff__Y_TmP_mydata.json'
    if not os.path.exists(data_path):
        # read defaults
        data = {
            '_del_orphaned': False,   # if true:  del orphaned versions
            '_demo_only': False,      # if true:  demo only
            '_hide_versions': False,  # if true:  hide
            '_symlinks_dirs': False,  # if false: ignore
            '_symlinks_files': False, # if false: ignore
            # 'scan_all_subdirs', # visibility of subdirs for power-user
            }
    else:
        # read from file
        with open(data_path, 'r') as f:
            data = json.load(f)

    if len(args) == 1:
        if args[0] == 'read_default_data':
            # return default dict
            return data
        else:
            # check .json file status
            if not os.path.exists(data_path):
                # write default data to disc
                with open(data_path, 'w') as f:
                   json.dump(data, f)
            else:
                # no action required here
                pass

    if len(args) == 2:
        # update a key-value
        data[args[0]] = args[1]
        # write updated data to disc
        with open(data_path, 'w') as f:
           json.dump(data, f)

    return data[args[0]]


print("rw_settings('read_default_data'):")
print(rw_settings('read_default_data'))

print("rw_settings('_demo_only'):")
print(rw_settings('_demo_only'))

print("rw_settings('read_default_data'):")
print(rw_settings('read_default_data'))

print("rw_settings('_demo_only', 'True'):")
print(rw_settings('_demo_only', 'True'))

print("rw_settings('_demo_only', 'True', 'one arg excess'):")
print(rw_settings('_demo_only', 'True', 'one arg excess'))

Die unten stehenden calls wurden nicht auf Vollständigkeit hin erstellt - sie stehen lediglich als einige Beispiele.

(Die ERROR-Behandlung ist hier als Platzhalter zu verstehen)
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
ulipy
User
Beiträge: 83
Registriert: Mittwoch 17. November 2021, 21:42
Wohnort: Ba-Wü

Verzeihung, falschen Knopf für die Links verwendet und nicht richtig registriert...
Py::: 1. funktional zuverlässig, 2. Anfänger-lesbar, 3. Py-Konformität
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dass die Anzahl der Argumente den Modus (Lesen oder Schreiben) festlegt, erinnert eher an ein Kommandozeilen-Tool. Innerhalb von Python würde man dafür besser zwei entsprechende Funktionen schreiben. Damit entfallen auch die komischen Prüfungen und die if-Verzweigungen. Ich würde auch gar nicht das Setzen und Holen der Werte kapseln, sondern das Dict direkt liefern. Dann bleibt es bei load_config(filename, default=DEFAULT_CONFIG) und save_config(cfg, filename). Wobei DEFAULT_CONFIG auf Modulebene steht und genutzt wird, falls der angegebene Dateiname nicht existiert. Dann schrumpft das Ganze auf 2 Dreizeiler-Funktionen + Zeilen für die DEFAULT_CONFIG.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Funktionen sollten nach ihren Tätigkeiten benannt werden. Ich nehme an das "rw" steht für read/write? Warum ist as beides in einer Funktion? Das tut man so nicht.

Du lässt der Funktion eine beliebige Anzahl von Parametern übergeben nur um dann hinterher die Anzahl der Parameter zu prüfen?
Das kann weg. Python kennt benannte Parameter und für die auch noch Default-Werte. Hier weist du irgenwdelchen Parametern magisch Fähigkeiten und Bedeutung zu. Das tut man so nicht.

Dein data_path zeigt auf das aktuelle Arbeitsverzeichnis. Der denkbar schlechteste Platz für wiederzufindende Dateien, denn der kann bei jedem Aufruf anders sein. Entweder irgendwo ins User-Verzeichnis oder zumindest in das Verzeichnis in dem das Script liegt.
Was ist dir bei dem Namen für die Datei passiert? Hast du den mit der Handrückseite getippt?

Warum habe die Schlüssel führende Unterstriche? Weg.
Die Kommentare sind in dieser Form überflüssig. Faustformel: Kommentare im Code sollen erklären _warum_ etwas getan wird, nicht was getan wird. Das sollte der Code erklären.

Jetzt prüfst du nicht nur auf die Anzahl der Argumente - der Wert des ersten Arguments kann auch noch magisch etwas anderes Auslösen als man denkt. Macht man so nicht.

Ich sehe im Moment auch nicht, wo dein Code mehr Vorteil bietet als ein simples Vorgehen mit je einer Methode zum Lesen und Schreiben eines kompletten dicts. Aber ich sehe sehr viele Nachteile.

Edit: snafu war schneller 8)
Antworten