Datenstruktur für variable Anzahl an Instanzen

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
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich bin mal wieder auf der Suche nach einer passenden Datenstruktur und hoffe ihr könnt mir wieder helfen.

Ich habe aktuell folgende Situation (das ist nur ein Beispielcode, damit man schnell sieht was das eigentliche Problem ist):

Code: Alles auswählen

COLOR = "BLUE"
NAME = "Superding"


class Machine:
    def __init__(self, color, name, power, torque):
        self.color = color
        self.name = name
        self.power = power
        self.torque = torque

    @property
    def speed(self):
        return self.power / self.torque


def get_data(excel):
    if excel.range("D2").value == "x":
        power_left = excel.range("A2").value
        power_right = excel.range("B2").value
        torque = excel.range("C3").value
        return [
            Machine(COLOR, NAME, power_left, torque),
            Machine(COLOR, NAME, power_right, torque),
        ]
    power = excel.range("B2").value
    torque = excel.range("C3").value
    return [Machine(COLOR, NAME, power, torque)]


def main():
    for machine in get_data(excel):
        print(machine.speed)


if __name__ == "__main__":
    main()

Jetzt finde ich es nicht schön die Klassen mit Argumenten händisch in eine Liste einzutragen. Vielleicht geht es jetzt auch noch, aber es gibt auch noch einen Fall, in dem ich drei Instanzen brauche. (Der ist noch nicht im Programm, weil ich den Stand erst mal ordentlich haben will)

Wie würdet ihr das machen?
Die Klassen habe ich, weil ich manche Eigenschaften für die Berechnungen öfters benötige und das eine Unmenge an Formeln sind, die ich nacheinander berechnen muss. Ich habe angefangen für jede Berechnung alle Werte als Argumente zu übergeben, das war total unübersichtlich und meiner Meinung nach ein rießen Chaos. Da dass hier meine berufliche Tätigkeit vereinfachen soll, würde ich den originalen Code ungern öffentlich teilen, da könnte dann schnell Firmen-Know-How auftauchen und dafür will ich nicht verantwortlich sein.

Ich hoffe ihr könnt mir trotz der dieses mal spärlichen Informationen ein paar Ratschläge geben.
Ich habe mir schon überlegt ob ich vielleicht schon mal eine Instanz mit den ganzen Konstanten anlegen könnte und wenn ich dann die Excel gelesen habe, müsste ich nur noch die Werte "hinzufügen". Also quasi in Richtung Vererbung, aber ob dass der richtige Weg ist?
Beim Versuch bin ich dann auf 'super' gestossen, aber man liest hier öfters "super ist super beschissen" und ja, letztendlich bin ich gerade total planlos und wollte mich jetzt nicht total verennen, da das Ganze auch neben dem Tagesgeschäft laufen sollte.

Ich sollte noch erwähnen, dass die Konstanten später Benutzereingaben sind. Also die jetzt einfach fest als Klassenkonstanten zu definieren, fliegt mir im übernächsten Schritt dann um die Ohren.

Bin auf eure Hinweise und Ratschläge gespannt.

Vielen Dank schon mal und viele Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich verstehe noch nicht ganz, wo Du jetzt genau das Problem siehst. Wenn Du mehrere Maschinen hast, brauchst Du mehrere Maschinen-Instanzen.
Du erzeugst aber die Maschinen-Instanzen sehr kompliziert an, weil Deine Datenstruktur (Excel) so kompliziert ist.
Das einzige, was Du anpassen mußt, ist die Codedopplungen in `get_data`. Da ich Deine wirkliche Struktur nicht kenne, hier nur anhand der Beispieldaten:

Code: Alles auswählen

def get_data(excel):
    torque = excel.range("C3").value
    if excel.range("D2").value == "x":
        power_cells = ["A2", "B2"]
    else:
        power_cells = ["B2"]
    return [
        Machine(COLOR, NAME, excel.range(cell).value, torque)
        for cell in power_cells
    ]
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Falls ich das Problem richtig verstanden habe, könnte man `partial()` verwenden, oder eine eigene lokale Funktion schreiben die ein `Machine`-Exemplar erzeugt:

Code: Alles auswählen

def get_data(excel):
    create_machine = partial(Machine, COLOR, NAME)
    
    if excel.range("D2").value == "x":
        torque = excel.range("C3").value
        return [
            create_machine(excel.range("A2").value, torque),
            create_machine(excel.range("B2").value, torque),
        ]

    return [create_machine(excel.range("B2").value, excel.range("C3").value)]
Oder man erzeugt in der Funktion erst einmal nur die ”nicht-festen” Argumente und erzeugt dann daraus am Ende die Exemplare in einer Schleife:

Code: Alles auswählen

def get_data(excel):
    if excel.range("D2").value == "x":
        torque = excel.range("C3").value
        arguments = [
            (excel.range("A2").value, torque),
            (excel.range("B2").value, torque),
        ]
    else:
        arguments = [(excel.range("B2").value, excel.range("C3").value)]

    return [Machine(COLOR, NAME, power, torque) for power, torque in arguments]
Oder man bastelt sich eine Builder-Klasse um das „builder pattern“ umzusetzen. Bin zu faul da selbst was zu Programmieren und verwende deshalb für's Beispiel mal `attr` und `bfa` (ungetestet):

Code: Alles auswählen

from attr import attrib, attrs
import bfa

COLOR = "BLUE"
NAME = "Superding"


@attrs(frozen=True)
class Machine:
    color = attrib()
    name = attrib()
    power = attrib()
    torque = attrib()

    @property
    def speed(self):
        return self.power / self.torque


def get_data(excel):
    builder = bfa.builder(Machine).color(COLOR).name(NAME)

    if excel.range("D2").value == "x":
        builder = builder.torque(excel.range("C3").value)
        return [
            builder.power(excel.range(cell_name).value).build()
            for cell_name in ["A2", "B2"]
        ]

    return [
        builder.power(excel.range("B2").value)
        .torque(excel.range("C3").value)
        .build()
    ]
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für eure Antworten.

Ich kann mir vorstellen, dass ich mit euren Beispielen den Code ein schönes Stück übersichtlicher hin bekomme und werde das gleich morgen früh testen.
Gut ist auch, dass ich die Beispiele verstehe, naja abgesehen von einer Ausnahme:
Was ist denn 'bfa'?

Meine Suchmaschine zeigt mir zwar einen Treffer bei 'pypi' an, aber der führt ins Leere, dann gibts noch das Bit-Flip Attack, aber das ist ja was mit Neural Network, ja dann gibts noch was in Verbindung mit Flask. Sonst findet die Ente nicht viel.
PyCharm findet es auch nicht, gibts das denn noch?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Manul
User
Beiträge: 52
Registriert: Samstag 13. Februar 2021, 16:00

Mit einer Suche nach "import bfa" war's bei mir der erste Treffer: https://bfa.readthedocs.io/en/latest/
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Dankeschön, ich geb immer sowas wie "python bfa" ein.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend,

ich wollte eine kurze Rückmeldung geben. Aktuell habe ich die Variante von Sirius3 eingebaut. Das ist vorerst übersichtlich und hatte noch keine Zeit mich intensiver mit 'bfa' und 'attr' auseinander zu setzen. Will das aber auf jeden Fall noch testen, bis jetzt ist nichts in Stein gemeiselt.

Ich bin der Meinung, das ich für meine Verhältnisse jetzt eine recht gute Struktur habe. Allerdings bin ich an dem ein oder anderen Punkt noch etwas am zweifeln.
Ich versuch den Zweifel mal zu erklären. Nehmen wir an ich habe eine Klasse, die eine Pumpe repräsentiert, die hat ein paar Eigenschaften auf Grund ihrer Bauform, daran gibt es nichts zu ändern. Über diese Eigenschaften kann ich weitere Eigenschaften die mich interessieren berechnen. Dafür nutze ich @property.
Die Pumpe muss ja irgendwas fördern, vielleicht irgend eine gemischte Flüssigkeit. Dafür habe ich auch eine Klasse, die bekommt dann die Bestandteile ink. Menge, die Temperatur und vielleicht noch den Druck mit dem die Flüssigkeit gerade gespeichert ist. Hier gibt es eine Funktion, die die Zusammensetzung prüft und eine Funktion, die die Gemischeigenschaften berechnet und wieder ein paar Eigenschaften für die ich @property benutze.

Wenn ich die Flüssigkeit jetzt mit meiner Pumpe ansauge und sie irgendwohin beefördere, dann ändern sich die Eigenschaften der Flüssigkeit, zum Beispiel der Druck oder die Geschwindigkeit. Weil meine Pumpe für die Eigenschaftsänderung verantwortlich ist, hat sie eine Funktion bekommen in der die neue Geschwindigkeit berechnet wird. Dafür bekommt sie aber eine eine Eigenschaft von der Flüssigkeitsklasse übergeben um die Geschwindigkeit berechnen zu können.
Sehr ihr die Struktur auch so? Soll ich das überhaupt in der Klasse berechnen oder eine lokale Funktion schreiben, die die benötigten EIgenschaften aus den Klassen holt?
Ich brauche für einige Ergebnisse das Zusammenspiel beider Klassen und ich bin mir unschlüssig, wo die Berechnung stattfinden soll.

Sorry für den langen Text, habe mich erst danach entschieden, das es Sinn macht das Ganze in Code darzustellen. Aber löschen will ich den Text jetzt auch nicht mehr.
Meine aktuelle Situation, beispielhaft, ist so:

Code: Alles auswählen

class Pump:
    def __init__(self, power, torque):
        self.power = power
        self.torque = torque
        self.fluid_velocity = None

    @property
    def speed(self):
        return self.power / self.torque

    def calculate_velocity(self, fluid_property):
        self.fluid_velocity = self.speed * fluid_property


class FluidMix:
    def __init__(self, mix):
        self.fluid_mix = mix

    def calculate_fluid_mix(self):
        pass

    @property
    def spezific_gas_constant(self):
        return 1234


def main():
    pump = Pump(20, 5)
    fluid = FluidMix({"A": 50, "B": 50})
    fluid.calculate_fluid_mix()
    pump.calculate_velocity(fluid.spezific_gas_constant)
    print(pump.fluid_velocity)


if __name__ == "__main__":
    main()
Weil mein Gedanke ist, die Pumpe ist für die Eigenschaftsänderung verantwortlich und ich kann bei einem Verkauf der Pumpe zum Beispiel sagen, eine Eigenschaft ist, dass sie die Flüssigkeit auf eine Geschwindigkeit von xyz bringt.
Ich habe noch lokale Funktionen, die beziehen sich aber auf die Excel und darauf die Ergebnisse irgendwo hinzu schreiben. In der 'main' werden Eigenschaften und Funktionen der Klassen aufgerufen und eine 'if'-Abfrage ist noch drin.

Vielen Dank und ich hoffe ich löse damit jetzt keine größere Verwirrung aus.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 414
Registriert: Freitag 2. Dezember 2022, 15:49

Dennis89 hat geschrieben: Freitag 26. Mai 2023, 20:01 Dafür bekommt sie aber eine eine Eigenschaft von der Flüssigkeitsklasse übergeben um die Geschwindigkeit berechnen zu können.
Sehr ihr die Struktur auch so?
Nö.... ohne jetzt groß darüber nachzudenken: warum nur eine Eigenschaft? Warum nicht die Flüssigkeit komplett übergeben? Die relevante Eigenschaft kann sich die Funktion schon selbst aus der Flüssigkeit ziehen und hat bei Bedarf auch die restlichen Eigenschaften der Flüssigkeit im Zugriff. Wenn man später mal eine verbesserte/andere Berechnungsfunktion nutzt die dann vielleicht drei Eigenschaften benutzt, dann ändert sich zwar die Funktion, aber nicht die Schnittstelle und damit auch nicht die ....vielen Stellen an der die Berechnungsfunktion aufgerufen wird.

So viel jetzt dazu.

etwas Offtopic: Nach einer kleinen Fahrradtour muss ich jezt mal selbst in Sachen Flüssigkeiten aktiv werden.... Prost!
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Probleme die ich mit dem Code habe sind beispielsweise das `calculate_velocity()` nicht danach klingt als würde das eine Eigenschaft von `Pump`-Objekten ändern. Und zumindest der Beispielcode sieht auch nicht so aus als wäre das sinnvoll, denn statt das Attribut zu ändern und im nächsten Schritt abzufragen, könnte man `calculate_velocity()` das Ergebnis der Berechnung auch zurückgeben lassen.

Bei `calculate_fluid_mix()` im Grunde das gleiche. Unerwarteterweise scheint das den Zustand vom `FluidMix` zu verändern. Und hier scheint es auch so zu sein, dass das grundsätzlich immer nach der `__init__()` aufgerufen werden muss, damit das Objekt dann *tatsächlich* in einem benutzbarem Zustand ist? Dann gehört das mit in die `__init__()` oder in eine `classmethod()`.

Was bei `FluidMix` auch ein Alarmzeichen ist: die Attribute beschreiben ja woraus ein `FluidMix` besteht. Hier also das ein `FluidMix` aus einem `fluid_mix` besteht‽ Das riecht extrem komisch das ein Ding aus sich selbst besteht. Und wenn man einen `FluidMix` erstellt hat, also man hat dann ja schon einen, wird mit `calculate_fluid_mix()` ein `fluid_mix` berechnet‽
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für eure Antworten.

@grubenfox Wenn ich die ganze Flüssigkeit übergebe, dann ist die Klasse ja darauf angewiesen. Anders kann ich Funktionen der Klasse auch nutzen, ohne das sie von der Flüssigkeitsklasse wissen muss. Bzw. ich kann die Eigenschaft die für die Berechnung benötigt wird auch als Eingabewert darstellen oder so. Zumindest dachte ich, die Klasse ist so flexibler und unabhängiger.

@__blackjack__ 'calculate_velocity' verändert nicht die Eigenschaft der Pumpe. Die Pumpe verändert die Eigenschaft der Flüssigkeit. Also eher in die Flüssigkeits-Klasse damit?
Das ich nach der Berechnung gleich eine Zeile später das Ergebnis abrufe liegt am Beispielcode. Ich benötige das Ergebnis einer Rechnung teilweise an verschiedenen Stellen, deswegen der Weg über das Attribut.

Ja das ist richtig, ich kann die Klasse erst nutzen, nach dem ich 'calculate_fluid_mix' aufgerufen habe und dazu gehört noch eine weitere Funktion die die Summe der Bestandteile prüft. (Ich habe das Beispiel mal etwas realistischer angepasst). In dem Zusammenhang habe ich mal '__call__' benutzt (jetzt auch mit im Beispiel), habs aber wieder rausgeworfen, weil wenn ich im Code 'fluid()' lese, nicht sofort weis, was da passiert.
In die '__init__' habe ich das bewusst nicht geschrieben, weil ich die einfach halten wollte. Ich hatte auch tatsächlich mal eine zeitlang ein TODO mit 'classmethod' im Code stehen, nach dem ich mir Erklärungen dazu angeschaut habe, war ich der Meinung, dass das für meinem Fall nicht passt. Aber in dem Fall habe ich das nicht richtig verstanden.

Ok, die Namen waren doof gewählt. An sich übergebe ich keinen "fertigen" Mix sondern nur die einzelnen Komponenten. Ich muss mir dann die Eigenschaften der Komponenten beschaffen und dann die Eigenschaft des Mixes berechnen.

Ich habe so weit ich das noch im Kopf habe, die FluidMix etwas ausführlicher geschrieben. Hab erst am Mon.. ähm Dienstag (Juhu Feiertag) wieder Zugriff auf den tatsächlichen Code. Das Prinzip jetzt ist aber sicherlich sehr nahe am tatsächlichen.

So hier das überarbeitete Beispiel, ist auch lauffähig und man kann ein bisschen mit Stoffen spielen wenn man Lust hat. Die hart codierten Zahlen sind nur im Beispiel so.

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI


class Pump:
    def __init__(self, power, torque):
        self.power = power
        self.torque = torque
        self.fluid_velocity = None

    @property
    def speed(self):
        return self.power / self.torque

    def calculate_velocity(self, fluid_property):
        self.fluid_velocity = self.speed * fluid_property


class FluidMix:
    def __init__(self, fluids_components):
        self.fluid_components = fluids_components
        self.fluid_data = {}

    def __call__(self):
        print(self.check_number_of_fluid_components())
        self.get_fluid_data()

    def check_number_of_fluid_components(self):
        # TODO Fehlerbehandlung fehlt noch
        return sum(self.fluid_components.values()) == 100

    def get_fluid_data(self):
        for name, percentage in self.fluid_components.items():
            self.fluid_data[name] = {
                "specific_heat": PropsSI("C", "T", 293, "P", 101325, name)
            }
            self.fluid_data[name]["density"] = PropsSI("D", "T", 293, "P", 10325, name)

    @property
    def specific_heat(self):
        return sum(
            fluid["specific_heat"] * self.fluid_components[name] / 100
            for name, fluid in self.fluid_data.items()
        )

    @property
    def density(self):
        return sum(
            fluid["density"] * self.fluid_components[name] / 100
            for name, fluid in self.fluid_data.items()
        )


def main():
    pump = Pump(20, 5)
    fluid = FluidMix({"Water": 50, "Ethanol": 50})
    fluid()
    print(fluid.density)
    print(fluid.specific_heat)
    pump.calculate_velocity(fluid.density)
    print(pump.fluid_velocity)


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 414
Registriert: Freitag 2. Dezember 2022, 15:49

Dennis89 hat geschrieben: Freitag 26. Mai 2023, 22:24 @grubenfox Wenn ich die ganze Flüssigkeit übergebe, dann ist die Klasse ja darauf angewiesen. Anders kann ich Funktionen der Klasse auch nutzen, ohne das sie von der Flüssigkeitsklasse wissen muss. Bzw. ich kann die Eigenschaft die für die Berechnung benötigt wird auch als Eingabewert darstellen oder so. Zumindest dachte ich, die Klasse ist so flexibler und unabhängiger.
So, mittlerweile ist etwas Zeit vergangen, ich habe etwas nachgedacht und sage jetzt über meine vorherige Antwort: alles Falsch...

Aber dem "Nö ...." bleibe ich treu. :)
Erstmal zu dem obigen:
Wenn ich mal etwas spitzfindig sein darf, dann stelle ich fest dass du auch ohne Flüssigkeit die Funktionen der Pumpen-Klasse nutzen kannst. Nur nicht diejenigen Funktionen die mit einer Flüssigkeit zusammenhängen. ;) Die Klasse wird wohl so flexibler und unabhängiger, aber will man das/muss das so sein? Was ist eine Pumpe ohne irgendein Fluidum das sie pumpen kann?
Für mich entweder 'Staubfänger' oder 'Ersatzteil' oder so...

Mit etwas nachdenken bin ich jetzt an der Stelle, wo ich sage:
Nö.... warum etwas übergeben? Die Flüssigkeit, die die Pumpe pumpt, ist eine der Eigenschaften der Pumpe (nämlich das was vorne reinkommt... ). Und auf der anderen Seite kommt eine veränderte Flüssigkeit wieder raus.

[Das muss erst mal reichen, Code kommt später...]
Benutzeravatar
grubenfox
User
Beiträge: 414
Registriert: Freitag 2. Dezember 2022, 15:49

Unten steht der ungetestete Code... (ich hoffe er funktioniert trotzdem)
Die richtigen/besseren Namen für die Seite der Pumpe wo die Flüssigkeit eingesaugt wird und wo die Flüssigkeit rausgepumpt wird, dürft ihr euch aussuchen. Ich habe da keinen Plan von den passenden Pumpen-Begriffen...

Die Unterschiede:
Die Flüssigkeit die in die Pumpe rein geht, wird auf der suction_side angeschlossen. Könnte man auch gleich beim init mit anschliessen.

Code: Alles auswählen

    def __init__(self, power, torque, fluid=None):
Habe ich hier nicht gemacht. Und die Seite wo die Flüssigkeit raus gepumpt wird ist dann die pressure_side und dort fließt die Flüsigkeit dann mit geänderte Velocity.

Die velocity habe ich in Fluid einfach so reingeprügelt. Wenn man das ordentlich macht, dann ist das vermutlich ein weiteres Element in FluidMix.fluid_data

Damit wird eben gar nichts übergeben, sondern die geänderte Flüssigkeit fließt einfach auf der Pressure-Side aus der Pumpe raus...
(und wenn man die Pumpe ohne Flüssigkeit betreibt, dann geht sie auch gleich kaputt: :) ein None als fluid_in hat eben kein .density für die velocity-Berechnung.)

Nun der Code...

Code: Alles auswählen

import copy

from CoolProp.CoolProp import PropsSI


class Pump:
    def __init__(self, power, torque):
        self.power = power
        self.torque = torque
        self.fluid_in = None

    @property
    def speed(self):
        return self.power / self.torque

    def suction_side(self, fluid):
        self.fluid_in = fluid

    def _calculate_velocity(self, fluid_property):
        return self.speed * fluid_property

    def pressure_side(self):
        fluid_out = copy.deepcopy(self.fluid_in)
        fluid_out.velocity = self._calculate_velocity(self.fluid_in.density)
        return fluid_out

class FluidMix:
    def __init__(self, fluids_components):
        self.fluid_components = fluids_components
        self.fluid_data = {}
        self.velocity = 0

    def __call__(self):
        print(self.check_number_of_fluid_components())
        self.get_fluid_data()

    def check_number_of_fluid_components(self):
        # TODO Fehlerbehandlung fehlt noch
        return sum(self.fluid_components.values()) == 100

    def get_fluid_data(self):
        for name, percentage in self.fluid_components.items():
            self.fluid_data[name] = {
                "specific_heat": PropsSI("C", "T", 293, "P", 101325, name)
            }
            self.fluid_data[name]["density"] = PropsSI("D", "T", 293, "P", 10325, name)

    @property
    def specific_heat(self):
        return sum(
            fluid["specific_heat"] * self.fluid_components[name] / 100
            for name, fluid in self.fluid_data.items()
        )

    @property
    def density(self):
        return sum(
            fluid["density"] * self.fluid_components[name] / 100
            for name, fluid in self.fluid_data.items()
        )


def main():
    pump = Pump(20, 5)
    fluid = FluidMix({"Water": 50, "Ethanol": 50})
    fluid()
    print(fluid.density)
    print(fluid.specific_heat)
    pump.suction_side(fluid)
    fluid_out = pump.pressure_side()
    print(fluid_out.velocity)


if __name__ == "__main__":
    main()
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

Danke für deine Zeit zur späten Stunde.
Ich verstehe deine Gedanken, meine sind allerdings noch etwas chaotisch. Ich denke in einer ruhigen Stunde darüber nach, ob dein Ansatz auf den tatsächlichen Code übertragbar ist und welche Vor- und Nachteile ich davon habe.
Spätestens am Dienstag wenn ich den Code vor mir habe.

Deswegen kann ich dazu gerade auch leider gar nicht mehr schreiben, aber kommentarlos wollte ich das auch nicht stehen lassen. Ich gebe dir so früh wie möglich auf jeden Fall eine ausführliche Rückmeldung dazu.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 414
Registriert: Freitag 2. Dezember 2022, 15:49

[Das mit den chaotischen Gedanken kenne ich. Darum mache ich nächste Woche auch internet-freien Urlaub. Also keine unnötige Hektik hier, ich werde mir das ganze erst übernächste Woche wieder anschauen...
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Dein FluidMix ist ja immer noch nicht nutzbar, nachdem __init__ aufgerufen worden ist. Wobei ich überhaupt keinen Grund sehe, das nicht zu ändern.
Die Anteile am Mix sollte nicht in Prozent sein, normalerweise kann man die Anteile frei wählen und bei Bedarf normalisieren.
__call__ hat einen Sinn, wenn die Klasse tatsächlich wie eine Funktion wirken soll, hier ist es absolut sinnfrei. Bei get erwartet man, dass etwas zurückgegeben wird, get_fluid_data gibt aber nichts zurück.
Warum setzt Du einen Teil des Wörterbuchs direkt in den {} und einen anderen Teil gleich in der nächsten Zeile per Indexzuweisung?

Code: Alles auswählen

class FluidMix:
    def __init__(self, fluid_components):
        norm = sum(fluid_components)
        self.fluid_data = {
            name: {
                "specific_heat": PropsSI("C", "T", 293, "P", 101325, name),
                "density": PropsSI("D", "T", 293, "P", 10325, name),
                "portion": portion / norm,
            }
            for name, portion in fluid_components.items()
        }

    @property
    def specific_heat(self):
        return sum(
            fluid["specific_heat"] * fluid["portion"]
            for fluid in self.fluid_data.values()
        )

    @property
    def density(self):
        return sum(
            fluid["density"] * fluid["portion"]
            for fluid in self.fluid_data.values()
        )

Entweder hast Du eine Pumpe, die fest mit einer Flüssigkeit verbunden ist, oder Du willst eine generische Pumpe, die mit verschiedenen Flüssigkeiten gefüttert werden kann. Je nachdem gibt es zwei Lösungen:

Code: Alles auswählen

class Pump:
    def __init__(self, power, torque, fluid):
        self.power = power
        self.torque = torque
        self.fluid = fluid

    @property
    def speed(self):
        return self.power / self.torque

    @property
    def fluid_velocity(self):
        return self.speed * self.fluid.density

def main():
    fluid = FluidMix({"Water": 1, "Ethanol": 1})
    pump = Pump(20, 5, fluid)
    print(fluid.density)
    print(fluid.specific_heat)
    print(pump.fluid_velocity
oder

Code: Alles auswählen

class Pump:
    def __init__(self, power, torque, fluid):
        self.power = power
        self.torque = torque

    @property
    def speed(self):
        return self.power / self.torque

    def fluid_velocity(self, fluid):
        return self.speed * fluid.density

def main():
    pump = Pump(20, 5)
    fluid = FluidMix({"Water": 1, "Ethanol": 1})
    print(fluid.density)
    print(fluid.specific_heat)
    print(pump.fluid_velocity(fluid))
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo und danke für die Antwort.

Ich wollte mit meinem zweiten Codebeispiel den Ist-Zustand deutlicher zeigen und meinen Versuch mit '__call__' darstellen. Deswegen ist die FluidMix-Klasse nach der __init__ in dem Beispiel immer noch nicht brauchbar.
Dann werde ich den Weg auch so gehen und hole mir die Stoffwerte gleich in der __init__ in das Wörterbuch.
Da die Art des Stoffes und die Menge mal Eingabewerte werden, könnte ich die Eingabe auf 100% oder 1, je nach dem wie der Anteil angegeben wird, auch schon in der 'main' prüfen. Weil wenn das nicht hinhaut wird der Rest des Codes eh nicht ausgeführt.

Das mit dem Wörterbuch im Wörterbuch erstellen und dem Index-Zugriff ist entstanden, weil ich das mit dem Index schöner zum lesen fand. Das funktioniert aber nur wenn "hinter" 'self.fluid_data[name]' schon ein Wörterbuch ist. Das habe ich in der ersten Zeile erstellt um die folgenden Zeilen den Index nutzen zu können. Im Beispiel folgt nur eine Zeile, bei mir folgen noch ein paar mehr. Das waren meine Gedanken, als ich das geschrieben habe.
Wenn ich deinen Code anschaue, weis ich nicht wieso ich das so umständlich gemacht habe. Mein Argument mit der Lesbarkeit wird damit auch komplett widerlegt. Das ändere ich auf jeden Fall.

Über die zwei Lösungsvorschläge muss ich mir auch noch Gedanken machen, welche Vor- und Nachteile sich ergeben. Spontan bin ich bei der generischen Pumpe. Aber die ist auch ähnlich zu meinem Code, das könnte eine voreingenommene Entscheidung sein.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

(Ups, den Beitrag von Sirius3 hatte ich übersehen. Das Überschneidet sich jetzt etwas, sorry.)

@Dennis89: Du sagst im Beitrag `calculate_velocity()` verändere nicht die Eigenschaft der Pumpe, aber der Code sagt was anderes, denn die Attribute beschreiben die Eigenschaften der Pumpe und da ist `fluid_velocity` und das wird von `calculate_velocity()` verändert.

Das mit dem `__call__()` ist in der Tat nicht lesbar, weil der Leser da nicht weiss was passiert.

Bei `FluidMix` können bei den Attributen die `fluid_*`-Präfixe weg, denn das Ergibt sich ja schon aus dem Kontext.

Validierung und das Erstellen von `fluid_data` gehören mit in die `__init__()`. Nachdem die abgelaufen ist, sollte ein Objekt in einem benutzbaren Zustand sein. Falls nach der `__init__()` und vor dem benutzen *immer* zwingend eine weitere Methode aufgerufen werden muss, dann stimmt da was nicht. Falls man die `__init__()` etwas einfacher halten möchte, dann kann man auch `fluid_data` von aussen übergeben und das Erstellen davon in eine Klassenmethode auslagern.

Ungetestet:

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI


class Pump:
    def __init__(self, power, torque):
        self.power = power
        self.torque = torque

    @property
    def speed(self):
        return self.power / self.torque

    def calculate_velocity(self, fluid_property):
        return self.speed * fluid_property


class FluidMix:
    def __init__(self, components, data):
        self.components = components
        self.data = data

        if sum(self.components.values()) != 100:
            raise ValueError("components must add up to 100%")

        if self.components.keys() != self.data.keys():
            raise ValueError("components and data must have the same keys")

    def _calculate(self, property_name):
        return sum(
            fluid[property_name] * self.components[name] / 100
            for name, fluid in self.data.items()
        )

    @property
    def specific_heat(self):
        return self._calculate("specific_heat")

    @property
    def density(self):
        return self._calculate("density")

    @classmethod
    def from_components(cls, components):
        return cls(
            components,
            {
                component_name: {
                    property_name: PropsSI(
                        property_code, "T", 293, "P", 101325, component_name
                    )
                    for property_name, property_code in [
                        ("density", "D"),
                        ("specific_heat", "C"),
                    ]
                }
                for component_name in components.keys()
            },
        )


def main():
    pump = Pump(20, 5)
    fluid = FluidMix.from_components({"Water": 50, "Ethanol": 50})
    print(fluid.density)
    print(fluid.specific_heat)
    print(pump.calculate_velocity(fluid.density))


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Habt ihr es klicken hören? Bei mir hat es auf jeden Fall gerade "klick" gemacht und ich meine verstanden zu haben, wie das mit der 'classmethod' funktioniert. 🤓

Danke für die Antwort, jetzt habe ich ja mehr als genügend Infos um das alles in eine klare Struktur zu bringen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1125
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend,

ich habe mich dafür entschieden eine generische Pumpe zu schreiben. Das macht meiner Meinung nach am meisten Sinn, vorallem wenn ich daran denke wie ich das Programm noch erweitern könnte. Und es ist für mich auch klarer/übersichtlicher.

Ich habe auch die 'classmethod' eingebaut, der Grund dafür war aber eher für mich zur Übung, um damit vertrauter zu werden.

Ich hab noch eine Frage zu dem Projekt (das ist jetzt nicht notwendig aber es interessiert mich schon längers).
Ich habe zwei Python-Dateien und damit das läuft, braucht man noch 'Coolprop' und 'loguru'. Kann ich daraus ein Package erstellen, dass die Abhängigkeiten "automatisch" installiert? Also vielleicht mit einem ausführen einer 'setup'-Datei oder so? Oder anders, wie würde man da jetzt vorgehen wenn man das Projekt unkompilziert teilen will? Wenn ich das einfach einem Kollegen geben will, der nicht viel machen soll außer mit den Eingabewerten spielen und die Ergebnisse bewerten. Wäre nervig, wenn man erst alle Abhängigkeiten einzeln installieren muss und ich denke so arbeitet ihr Programmierer im Team auch nicht?
In einem anderen Projekt an dem ich mitarbeite, da habe ich Zugriff auf das Projekt über Gitlab bekommen und das installiert mir die Abhängigkeiten auch relativ unkompliziert. Da ist aber noch Docker im Spiel.

Ich weis, dass ich die Dateien in einen Ordner packe, eine '__init__.py'-Datei dazu, damit das mit dem suchen der Importe richtig funktioniert und dann kann ich noch eine 'requirements.txt'-Datei erstellen lassen (mit 'pip freeze > requirements.txt').
Macht man dass dann jetzt so das ich eine 'setup.py' Datei erstelle wie hier beschrieben mit dem aus dem Link beispielhaften Inhalt:

Code: Alles auswählen

from setuptools import setup

setup(
   name='foo',
   version='1.0',
   description='A useful module',
   author='Man Foo',
   author_email='foomail@foo.example',
   packages=['foo'],  #same as name
   install_requires=['wheel', 'bar', 'greek'], #external packages as dependencies
)
Mich wundert es, dass ich da 'requiers' angeben muss, obwohl ich doch die requirements.txt Datei habe.
Auch hier im QuickStart ist das mit 'requires' drin.

Viel Text, aber eigentlich sind die Fragen: Macht man das so? Fehlt noch was außer die Readme-Datei? Wie ist das mit den Abhängigkeiten?

Vielen Dank und Grüße
Dennis

Noch zum Schluss: Ich habe ja mal mit eurer Hilfe den Frage-Trainer erstellt und den könnte man auch noch mit 'requirements.txt' etc fertigstellen.
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten