Redundanzen reduzieren

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
mechanicalStore
User
Beiträge: 124
Registriert: Dienstag 29. Dezember 2009, 00:09

Hallo,

unten ein paar Ausschnitte (so nicht lauffähig, aber hoffentlich ausreichend für die Erklärung). Das Ganze läuft fehlerfrei. Jedoch ist die eigentliche Frage im Titel des Thread.

Grundsätzlich wird eine csv eingelesen, die etwas merkwürdige Spaltenbezeichnungen hat (beginnen z.B. alle mit einem whitespace). Daher hatte ich constants.py angelegt und diese Strings erstmal Konstanten zugewiesen, da ich nicht weiß, ob sich das Format der csv nicht irgendwann mal ändert. Bei import.csv diese dann einfach benutzt (Es handelt sich statt der 3 Einträge im Beispiel aber tatsächlich um 25 Einträge).

Spätestens beim Hinzufügen von gridmodel.py war klar, dass ich dort zum einen die Spaltennamen übernehmen muss (zu denen ich die Namen der Konstanten verwende, nicht die Original Namen der csv), zum anderen auch die Objekt-Attribute benötige. Also habe ich zwei Dictionarys erstellt, einmal csv-seitig und einmal qt-seitig. Zum einen habe ich jetzt schon eine gewisse Redundanz in constants.py (wobei das ja noch überschaubar wäre), zum anderen aber auch zwischen object_attributes und der eigentlichen Klassendefinition in sqlmodel.py, sowie in den Zuweisungen von csvmodel.py.

Lässt sich das alles kompakter gestalten?

Code: Alles auswählen

# constants.py
DELIMITER = ';'
CAA_NAME = " CAA.Name" # ----> Ursprünglich, jetzt durch dictionary ersetzt

csv_titles = {"CAA_NAME" : " CAA.Name", 
                "CAA_TYPE" : " CAA.Type",
                "CAA_ORIGIN_X" : " CAA.Origin.X",
                ...


object_attributes = {"CAA_NAME" : "point_name", 
                      "CAA_TYPE" : "point_type",
                      "CAA_ORIGIN_X" : "point_origin_x",
                      ...

# csvmodel.py
import csv
from constants import csv_titles as ct
from constants import DELIMITER
from sqlmodel import MeasuringPiece

def import_csv(session, m_id, m_filename, offset):
    with open(m_filename, newline='') as csvfile:
        reader = csv.DictReader(csvfile, delimiter=DELIMITER)
        for row in reader:
            o_point = MeasuringPiece(measuring_id = m_id)
            o_point.point_name = row[ct["CAA_NAME"]].strip()
            o_point.point_type = row[ct["CAA_TYPE"]].strip()
            o_point.point_origin_x = float(row[ct["CAA_ORIGIN_X"]].strip().replace(',', '.'))
            ...

# sqlmodel.py
class MeasuringPiece(Base):
    __tablename__ = "part_measuring_piece"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    measuring_id: Mapped[int] = mapped_column(ForeignKey("part_measuring.id"))
    measuring: Mapped["Measuring"] = relationship(back_populates="measuring_pieces")

    point_name: Mapped[str] = mapped_column(String(10))
    point_type: Mapped[str] = mapped_column(String(5))
    point_origin_x: Mapped[float]
    ...

# gridmodel.py
from sqlmodel import MeasuringPiece
from constants import (
    csv_titles, 
    object_attributes)

class PointGridModel(QAbstractTableModel):
    def __init__(self, session):
        super().__init__()

        self.column_labels = [v for k, v in csv_titles.items()]
        self.column_attributes = [v for k, v in object_attributes.items()]
        self.point_data = MeasuringPiece.get_points(session)
        ...
Benutzeravatar
__blackjack__
User
Beiträge: 13129
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Warum wird in `gridmodel` `csv_titles` importiert? Die Informationen sind doch alle in `object_attributes` vorhanden‽

Beide Namen sollten wie Konstanten geschrieben werden, und bei Wörterbüchern ist ein `key_to_value` Namensschema sinnvoll. Also COLUMN_NAME_TO_CSV_COLUMN_NAME und `COLUMN_NAME_TO_MODEL_ATTRIBUTE_NAME` beispielsweise.

Ich sehe nicht so ganz warum das in einem eigenen Modul sein muss. Also zumindest Spalten- zu Model-Attributnamen gehört IMHO in das GUI-Modul. Bei den CSV-Spaltennamen könnte man noch argumentieren, dass die eher zu Konfiguration gehören. Wobei das da vielleicht auch eher eine Abbildung von den tatsächlichen Spaltennamen auf Attributnamen sein sollte. Plus vielleicht Konvertierungsfunktionen.

Warum heissen `MeasuringPiece`-Objekte `o_point`? Was bedeutet das `o_`? Und wenn das immer Punkte sind, dann ist das `point_` in den Attributnamen redundant. Bei `m_id` und `m_filename` kann man sich denken was das `m_` bedeuten soll, aber auch da sollte man nicht rätseln müssen.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 124
Registriert: Dienstag 29. Dezember 2009, 00:09

Hallo __blackjack__

Danke für die vielen Hinweise.
__blackjack__ hat geschrieben: Donnerstag 18. April 2024, 11:15 @mechanicalStore: Warum wird in `gridmodel` `csv_titles` importiert? Die Informationen sind doch alle in `object_attributes` vorhanden‽
In der Tat hast Du da recht, die Möglichkeit habe ich tatsächlich übersehen.
Ich sehe nicht so ganz warum das in einem eigenen Modul sein muss. Also zumindest Spalten- zu Model-Attributnamen gehört IMHO in das GUI-Modul.
Grund war, dass bei Änderungen Beide in geicher Weise betroffen sind und es somit nur an einer Stelle übersichtlich geändert werden könnte.
Bei den CSV-Spaltennamen könnte man noch argumentieren, dass die eher zu Konfiguration gehören. Wobei das da vielleicht auch eher eine Abbildung von den tatsächlichen Spaltennamen auf Attributnamen sein sollte. Plus vielleicht Konvertierungsfunktionen.
Aha. Wenn ich Dich also richtig verstanden habe, hätte ich dann etwas in der Art von:

Code: Alles auswählen

# constants.py
CSV_COLUMN_NAME_TO_MODEL_ATTRIBUTE_NAME = {" CAA.NAME" : "point_name",
...

# gridmodel.py
GRID_COLUMN_NAME_TO_MODEL_ATTRIBUTE_NAME = {"Punkt-Name" : "point_name",
...
Wobei das in gridmodel.py ebenfalls auf Modulebene wäre und dann in der __init__ wie bisher in die beiden Listen aufgeteilt wird.

Welche Konvertierungsfunktionen wären das? Bisher hatte ich da keine Notwendigkeit, aber Ideen sind natürlich immer sehr willkommen.
Warum heissen `MeasuringPiece`-Objekte `o_point`? Was bedeutet das `o_`? Und wenn das immer Punkte sind, dann ist das `point_` in den Attributnamen redundant. Bei `m_id` und `m_filename` kann man sich denken was das `m_` bedeuten soll, aber auch da sollte man nicht rätseln müssen.
Angekommen, danke.
Benutzeravatar
__blackjack__
User
Beiträge: 13129
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bei Konvertierungsfunktionen meinte ich das was in `import_csv()` mit den einzelnen Spaltenwerten gemacht wird. Also so etwas in dieser Art:

Code: Alles auswählen

CSV_COLUMN_NAME_TO_TARGET = {
    " CAA.Name": ("point_name", str),
    " CAA.Type": ("point_type", str),
    " CAA.Origin.X": (
        "point_origin_x",
        lambda text: float(text.replace(",", ".")),
    ),
    # ...
}


def import_csv(session, measuring_id, filename, offset):
    with open(filename, newline="") as csvfile:
        for row in csv.DictReader(csvfile, delimiter=DELIMITER):
            point = MeasuringPiece(
                measuring_id=measuring_id,
                **{
                    attribute_name: convert(row[column_name].strip())
                    for column_name, (
                        attribute_name,
                        convert,
                    ) in CSV_COLUMN_NAME_TO_TARGET.items()
                }
            )
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 124
Registriert: Dienstag 29. Dezember 2009, 00:09

@__blackjack__: Danke für den sehr abstrahierten code. Da blicke ich jetzt nicht mehr durch. :-) in den Docs finde ich nichts zu **{} (zuindest nicht unter Lexical analysis). Kannst Du das mal erklären?

Danke vorab und Gruß
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist das Gegenstück zum catchall-Argumenten mit * und **.

funktion(**{ “keyword”:value})

ruft Funktion auf als ob du

funktion(keyword=value)

geschrieben hättest.
Benutzeravatar
__blackjack__
User
Beiträge: 13129
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Die Dokumentation hat einen praktischen Index (fast auf jeder Seite ist der oben rechts zu erreichen), da dann Symbols und dort beim ``**``-Eintrag die beiden Links zu „in function calls“: in der Sprachreferenz und im Tutorial. Letzteres hat ein Beispiel.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 124
Registriert: Dienstag 29. Dezember 2009, 00:09

@__deets__ , @__blackjack__ : Vielen Dank für die Erklärungen.
Antworten