Performance von `property`-Dekorator

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: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich habe noch eine weitere Frage, die mir gerade in den Kopf kam.
Ich mag den `property`-Dekorator ziemlich, bin mir aber nicht sicher ob das immer so sinnvoll ist.

Sagen wir ich habe eine Beispiel-Klasse `Gas`. Das Gas kann ein Gemisch aus unterschiedlichen Gasen sein. Für Berechnungen brauche ich beispielsweise die Dichte des Gasgemisches. Also hat die Funktion, die mir die Dichte berechnet ein `@property` bekommen. Die Klasse, mal auf die eine Eigenschaft herunter gebrochen sieht so aus:

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI


class Gas:
    def __init__(
        self,
        components,
        name_to_property,
    ):
        self.components = components
        self.name_to_property = name_to_property

        if sum(self.components.values()) != 100:
            raise ValueError(
                f"Components must be 100%, now -> {list(components.values())}"
            )

    @classmethod
    def from_components(
        cls,
        components,
        temperature,
        pressure,
    ):
        try:
            gas = cls(
                components,
                {
                    component_name: {
                        property_name: (
                            PropsSI(
                                property_code,
                                "T",
                                temperature,
                                "P",
                                pressure * 1e5,
                                component_name,
                            )
                            if property_name != "normdichte"
                            else PropsSI("D", "T", 273, "P", 1e5, component_name)
                        )
                        for property_name, property_code in [
                            ("normdichte", "D"),
                            ("dichte", "D"),
                            ("viskositaet", "V"),
                            ("waermeleitfaehigkeit", "L"),
                            ("waermekapazitaet", "CP0MASS"),
                            ("molemass", "molemass"),
                            (
                                "molar_gas_constant",
                                "gas_constant",
                            ),
                        ]
                    }
                    for component_name in components.keys()
                },
            )
        except ValueError:
            raise
        return gas

    def _calculate(self, property_name):
        return sum(
            properties[property_name] * self.components[name] / 100
            for name, properties in self.name_to_property.items()
            if self.components[name] > 0
        )

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

Sagen wir ich will jetzt 100 Maschinen berechnen, die alle dieses Gaseigenschaft zur Berechnung benötigen, vielleicht wird die Eigenschaft pro Berechnung 5 mal benötigt. Das würde bedeuten, dass die Dichte 500 mal neu berechnet wird und jedes mal das selbe Ergebnis kommt. Das ist ja schon recht unnötig. Mal allgemein betrachtet und unabhängig davon wie aufwändig die Berechnung ist.
Alternativ könnte ich so etwas machen:

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI


class Gas:
    def __init__(
        self,
        components,
        name_to_property,
    ):
        self.components = components
        self.name_to_property = name_to_property
        self.density = None

        if sum(self.components.values()) != 100:
            raise ValueError(
                f"Components must be 100%, now -> {list(components.values())}"
            )

    @classmethod
    def from_components(
        cls,
        components,
        temperature,
        pressure,
    ):
        try:
            gas = cls(
                components,
                {
                    component_name: {
                        property_name: (
                            PropsSI(
                                property_code,
                                "T",
                                temperature,
                                "P",
                                pressure * 1e5,
                                component_name,
                            )
                            if property_name != "normdichte"
                            else PropsSI("D", "T", 273, "P", 1e5, component_name)
                        )
                        for property_name, property_code in [
                            ("normdichte", "D"),
                            ("dichte", "D"),
                            ("viskositaet", "V"),
                            ("waermeleitfaehigkeit", "L"),
                            ("waermekapazitaet", "CP0MASS"),
                            ("molemass", "molemass"),
                            (
                                "molar_gas_constant",
                                "gas_constant",
                            ),
                        ]
                    }
                    for component_name in components.keys()
                },
            )
        except ValueError:
            raise
        return gas

    def _calculate(self, property_name):
        return sum(
            properties[property_name] * self.components[name] / 100
            for name, properties in self.name_to_property.items()
            if self.components[name] > 0
        )

    def calculate_density(self):
        self.density = self._calculate("dichte")

Und müsste halt einmal die `calculate_density` Funktion aufrufen. Dabei gefällt mir nicht das meine `__init__` eine Reihe von `None`-Werten zu Beginn hat.

Gibt es hier eine Alternative? Gibt es eine Fausformel, wann man `property` verwendet und wann nicht mehr und wie geht man vor, wenn man es nicht mehr verwendet?

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

Wie fast immer bei Performance-Fragen: Messen wie viel das im Vergleich zum Rest ausmacht und ob es sich lohnt da was dran zu optimieren.

Und vielleicht interessiert Dich `functools.cached_property()` wenn sich der Wert nicht ändert. Das wird nur einmal beim ersten Zugriff tatsächlich berechnet.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die schnelle Antwort.
Ob sich das normale `property` bemerkbar macht, weis ich noch nicht. Das kam mir nur in den Kopf, weil es ja sinnlos ist immer wieder für das gleiche Ergebnis eine Rechnung zu starten. Da wäre `cached_property` ja eine gute Alternative. Ist notiert 😊

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

So ähnlich wollte ich auch antworten (den berechneten Wert einfach cachen: nur dann berechnen wenn er noch nicht vorher berechnet wurde). Aber da fehlt ja noch der interessante Teil des Problems: Wenn sich ein Parameter eines Teils eines Gasgemisches ändert, dann nur diejenigen Werte neu berechnen, die von dieser Änderung betroffen sind.
Da hatte ich mal vor Jahren was im Web entdeckt... interessiert gelesen, mangels Anwendungsfall wahrscheinlich nie ernsthaft eingesetzt, irgendwo weit entfernt im Hinterkopf abgelegt und bis vorhin nicht mehr dran gedacht.

Aber als ich jetzt gesucht hatte und auf einmal über PyCells stolperte, da fing irgendwo ein leises Glöckchen an zu klingeln. Ich glaube das war's damals oder was anderes mit gleicher Funktionalität.

Ich empfehle jetzt ausdrücklich NICHT diesen alten Kram noch einzusetzen. Aber gibt es da was aktuelles? Was elegantes?
Benutzeravatar
__blackjack__
User
Beiträge: 13271
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Auch schon etwas älter aber jünger als PyCells: https://pypi.org/project/promised/
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
grubenfox
User
Beiträge: 454
Registriert: Freitag 2. Dezember 2022, 15:49

Aha, Danke! Muss ich mir mal in Ruhe anschauen....
Benutzeravatar
DeaD_EyE
User
Beiträge: 1038
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Angenommen, die Methode würde einfach nur ein Attribut der Instanz zurückgeben, so hat man durch die Indirektion mindestens einen Funktionsaufruf mehr. Über das Problem denkt man nach, wenn die Eigenschaft eine Million mal hintereinander aufgerufen wird. Dann misst man. Dann überprüft man, ob ein Cache sinnvoll ist. Wenn etwas berechnet werden muss, kann das Zeit sparen. Wenn nur auf ein Attribut zugreifen möchte, ist der direkte Zugriff auf das Attribut schneller, aber auch da gilt: Messen
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

danke für die Antwort.
Die Frage bezog sich auf eine Eigenschaft, die wirklich jedes mal berechnet wird. Wenn ich nur das Attribut will und das auch immer den gleichen Wert hat, dann würde ich kein `property` verwenden.

Ich habe mich in letzter Zeit auch noch gefragt, ob die Klasse Sinn macht. Wenn wir den ersten Code nehmen, den ich hier im Eingagnspost gezeigt habe und den um weitere Eigenschaften erweitern, die alle alle mit `_calculate` berechnet werden und mehr passiert in der Klasse nicht. Ist dass dann vom weiteren Programmablauf abhängig ob das mit einer Klasse verständlicher und übersichtlicher ist oder schreibt man für so etwas keine Klasse? Ich merke mir an sich ja nicht wirklich einen Zustand und meine Funktionen, die etwas mit den Gaseigenschaften rechnen, bekommen nicht die Klasse übergeben, sondern nur die Eigenschaften, die sie benötigen.
Ich finde das mit der Klasse eigentlich schön, aber das hat ja nichts zu sagen.

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

Dennis89 hat geschrieben: Donnerstag 30. Mai 2024, 08:38 Die Frage bezog sich auf eine Eigenschaft, die wirklich jedes mal berechnet wird. Wenn ich nur das Attribut will und das auch immer den gleichen Wert hat, dann würde ich kein `property` verwenden.
Naja, muss die denn wirklich jedes mal berechnet werden oder nur dann wenn sich die zugrunde liegenden Parameter (z.B. das Gasgemisch [z.B. 5% Sauerstoff, 70% Helium, 20% Stickstoff, 5% Xenon bei 20°C]) ändern?

Wenn die Eigenschaft dann ein weiteres mal mit identischen Parametern abgefragt wird, reicht es die vorher einmal berechneten und gespeicherten Werte zurück zu liefern? Falls 'Ja', klingt das für mich nach einem Dictionary zusammen mit einer Funktion/Methode/Klassenmethode die den Zugriff auf dieses Dictionary regelt und gegebenenfalls kapselt.
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dennis89: Du hast bei Deinem Klassendesign eine komische Mischung aus vorberechneten Größen und dann ein paar Größen, die Du per Property berechnen möchtest.
Ein reines Gas ist bei Dir dann aber nur ein Name mit einem Anteil, ein Gas-Gemisch ist dagegen eine komplexe Klasse. Das ist doch vom Design wieder unverständlich.
Und dann sind Deine Properties gar keine Properties, weil die ja von Temperatur und Druck abhängen.

Wenn ich das richtig sehe, brauchst Du ersteinmal eine Klasse für ein reinen Gas, das die schreckliche Syntax von diesem PropsSI in etwas nutzbares kapselt, und dann auf der selben Struktur ein Gas-Gemisch:

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI

class AbstractGas:
    def density(self, temperature, pressure):
        return self._property_si(temperature, pressure, "D")

    def viscosity(self, temperature, pressure):
        return self._property_si(temperature, pressure, "V")

    def thermal_conductivity(self, temperature, pressure):
        return self._property_si(temperature, pressure, "L")

    def thermal_capacity(self, temperature, pressure):
        return self._property_si(temperature, pressure, "CP0MASS")


class Gas(AbstractGas):
    def __init__(self, name):
        self.name = name

    def _property_si(self, temperature, pressure, type):
        return PropsSI(type, "T", temperature, "P", pressure * 1e5, self.name)


class GasMixture(AbstractGas):
    def __init__(self, component_portions):
        self.component_portions = component_portions

    def _property_si(self, type, temperature, pressure):
        return sum(
            portion * component.property_si(temperature, pressure, type)
            for component, portion in self.component_portions
        )
Sollte das aus irgendwelchen Gründen tatsächlich Performance-Probleme machen, könnte man die _property_si-Methode mit functools.lru_cache dekorieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13271
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn die Properties alle nach dem Muster „wir rufen self._calculate()“ mit dem passenden Schlüssel auf, dann müsste man da kein ``def`` schreiben:

Code: Alles auswählen

    densitiy = property(partialmethod(_calculate, "dichte"))
Warum heisst das überhaupt "dichte" und nicht "density"? Ist das von aussen vorgegeben? Falls nicht, und man die Schlüssel so nennen kann wie man möchte, also auch so wie Attribute/Properties heissen sollten, kann man das alles auch über *eine* `__getattr__()` erledigen (ungetestet):

Code: Alles auswählen

    def __getattr__(self, property_name):
        if property_name in first(self.name_to_property.items(), {}):
            return sum(
                properties[property_name] * self.components[name] / 100
                for name, properties in self.name_to_property.items()
                if self.components[name] > 0
            )

        raise AttributeError(f"{self!r} has no attribute {property_name!r}")
Dazu dann noch eine passende `__dir__()`-Implementierung, damit „introspection“ weiterhin funktioniert.

`first()` ist aus `more_itertools` und könnte man sich natürlich sparen wenn man die Namen nicht nur in einer Methode in einer „comprehension“ stehen hat.

Aus OOP-Sicht frage ich mich wie `components` aussieht und ob es nicht eher Sinn machen würde die `Gas`-Klasse in `GasMix` umzubenennen und tatsächlich eine `Gas`-Klasse für ein ”reines” Gas einzuführen das aus *einer* Komponente erstellt wird. Und beiden Klassen mit der gleichen API auszustatten, so dass sich ein `Gas` genau so verhält und benutzt werden kann wie ein `GasMix` das zu 100% nur aus diesem einen Gas besteht.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
grubenfox
User
Beiträge: 454
Registriert: Freitag 2. Dezember 2022, 15:49

__blackjack__ hat geschrieben: Donnerstag 30. Mai 2024, 14:31 Aus OOP-Sicht frage ich mich wie `components` aussieht und ob es nicht eher Sinn machen würde die `Gas`-Klasse in `GasMix` umzubenennen und tatsächlich eine `Gas`-Klasse für ein ”reines” Gas einzuführen das aus *einer* Komponente erstellt wird. Und beiden Klassen mit der gleichen API auszustatten, so dass sich ein `Gas` genau so verhält und benutzt werden kann wie ein `GasMix` das zu 100% nur aus diesem einen Gas besteht.
Also wenn ein GasMix, welches zu 100% aus einer einzigen Gasart besteht, sich identisch verhält wie ein reines Gas (welches zu 100% aus einer einzigen Gasart besteht), dann erscheint mir die Klasse für das reine Gas sehr redundant und überflüssig. Wo ist denn da der Unterschied?
Da kann man doch das GasMix fragen aus wievielen Komponenten es besteht oder eine Methode/Attribut 'rein_oder_nicht_rein_das_ist hier_frage' aufrufen/abfragen....
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

@grubenfox: die Antwort auf deine Frage habe ich ja schon in meinem Post gegeben. Man braucht eine Klasse, die die Eigenschaften für ein Gas ermitteln kann. Die gasgemischklasse baut dann darauf auf.

@__blackjack__: für einen festen Satz an Attributen wäre mir __getattr__ zu magisch.
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

vielen Dank für eure Antworten.

-die Antwort von @__blackjack__ sah ich erst vor dem abschicken, daher erst die bereits geschriebene Antwort-

-Okay, die weiteren Antworten sehe ich erst jetzt vor dem zweiten abschicken. @grubenfox bitte fühle dich nicht ignoriert, aber deine Frage ging ja an blackjack. Trotzdem danke für deine Beteiligung.-

@Sirius3
So habe ich das noch nicht betrachtet. Meine Idee beim schreiben der Klasse war folgende. Ein Gasgemisch ist auch nur ein Gas und da ich im voraus nicht weis, ob ein reines Gas oder ein Gemisch berechnet werden soll, gibt es nur eine Klasse.
Ein reines Gas ist bei Dir dann aber nur ein Name mit einem Anteil, ein Gas-Gemisch ist dagegen eine komplexe Klasse.
Wenn ich für `components` ein reines Gas übergebe, sowas wie `{H2: 100}` dann wird das gleich behandelt, wie wenn ich ein Gemisch übergebe. Das ist zwar unnötig, weil die Summe von einem Bestandteil berechnet wird, aber das verhält sich nicht anders, wie ein Gemisch. Oder verstehe ich dich da falsch?
Und dann sind Deine Properties gar keine Properties, weil die ja von Temperatur und Druck abhängen.
Meine Berechnung betrachtet einen Betriebszustand und für diesen ist Druck und Temperatur bekannt und diese Gaseigenschaften benötige ich. Die Klasse bekommt Druck und Temperatur auch übergeben.
Oder hast du das geschrieben, weil meine Klasse dann nicht allgemeingültig ist? So wie du das machst, kann ich jeden beliebigen Gaszustand abfragen und die Klasse ist vielseitiger bzw. kann man für andere Berechnungen einfach wieder verwenden. Das ist natürlich gut, ich war auf meinen Berechnungsablauf fixiert und übergebe deswegen Temperatur und Druck beim erstellen des Gases, weil mich in diesem Fall nur dieser eine Zustand interessiert.

Die Idee und der Aufbau deines Vorschlags gefällt mir echt gut, weil es schon viel klarer zu lesen ist, wie meiner. Bin jetzt nur noch am überlegen, ob ich in meinem Berechnungsdurchgang jedes mal Druck und Temperatur übergebe oder ob ich deinen Vorschlag so abändere:

Code: Alles auswählen

from CoolProp.CoolProp import PropsSI


class AbstractGas:
    def density(self):
        return self._property_si("D")

    def viscosity(self):
        return self._property_si("V")

    def thermal_conductivity(self):
        return self._property_si("L")

    def thermal_capacity(self):
        return self._property_si("CP0MASS")


class Gas(AbstractGas):
    def __init__(self, name, temperature, pressure):
        self.name = name
        self.temperature = temperature
        self.pressure = pressure

    def _property_si(self, property_type):
        return PropsSI(
            property_type, "T", self.temperature, "P", self.pressure * 1e5, self.name
        )


class GasMixture(AbstractGas):
    def __init__(self, component_portions):
        self.component_portions = component_portions

    def _property_si(self, property_type):
        return sum(
            portion * component._property_si(property_type)
            for component, portion in self.component_portions.items()
        )


def main():
    gas = Gas("CO2", 300, 5)
    print(gas.density())
    gas_mix = GasMixture({Gas("CO2", 300, 5): 50, Gas("CO", 300, 5): 50})
    print(gas_mix.density())


if __name__ == "__main__":
    main()

@__blackjack__
Ja das wird alles nach dem gleichen Muster aufgerufen. Dass das ohne `def` geht wusste ich gar nicht.
Ich habe Beispiele dazu gefunden und gesehen dass ich das in von den Einrückungen her direkt an die Position meiner Funktion schreiben kann:

Code: Alles auswählen

    def _calculate(self, property_name):
        return sum(
            properties[property_name] * self.components[name] / 100
            for name, properties in self.name_to_property.items()
            if self.components[name] > 0
        )

    densitiy = property(fget=partialmethod(_calculate, "dichte"))
    
    
def main():
    gas = Gas.from_components({"CO2": 100}, 300 ,5)
    print(gas.densitiy)
    
if __name__ == '__main__':
    main()
`partialmethod` habe ich von `functools` importiert allerdings erhalte ich:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/dennis/PycharmProjects/CoolerCalculation/app/class.py", line 78, in <module>
    main()
  File "/home/dennis/PycharmProjects/CoolerCalculation/app/class.py", line 75, in main
    print(gas.densitiy)
          ^^^^^^^^^^^^
TypeError: 'partialmethod' object is not callable
Testweise ohne `property` kommt keine Fehlermeldung, da erhalte ich:

Code: Alles auswählen

functools.partial(<bound method Gas._calculate of <__main__.Gas object at 0x7f8452392350>>, 'dichte')
Warum heisst das überhaupt "dichte" und nicht "density"?
Das war ein langer Prozess, bis die Berechnung stand und da hatte ich einige Probleme und zu Beginn hatte ich noch deutsche Namen. Die habe ich im ersten Schritt dann in den Code übernommen, aber noch nicht angepasst. Um die Frage zu beantworten, die kann ich nennen wie ich will.

Auch `__getattr()__` habe ich noch nie verwendet, die Funktionsweise muss ich auch noch testen. Weis nur nicht ob mir das heute noch reicht, weil ich mich erst schlau machen muss, wie man das mit `__dir__` macht und was du genau mit „introspection“ meinst. Das wird vermutlich erst mal ein separates "Studium" :mrgreen:

`components` ist ein Wörterbuch und sollte wohl eher `gas_name_to_percent` heißen und sieht zum Beispiel so aus:

Code: Alles auswählen

{"CO2": 50, "H2": 50}
Zum Schluss, immer wieder schön zu lesen, dass ihr ohne (vermutliches) abzusprechen gleicher Meinung seid. Ich dachte echt, das sei mit einer Klasse ziemlich cool gelöst.

Und ganz zum Schluss noch mal danke für eure Hilfe.

Grüße
Dennis

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

@grubenfox: Da sollte halt kein Unterschied sein zwischen einem `GasMix` der zu 100% aus einem `Gas` besteht. Das `Gas` braucht man weil ja nicht jedes `GasMix` zu 100% aus *einem* `Gas` besteht. Das ist eine Invariante das sich reines `Gas` und `GasMix` aus 100% einem `Gas` gleich verhalten müssen. Täten sie das nicht, wäre das schon etwas komisch.

@Sirius3: Ja wäre mir auch ein wenig zu magisch, ausser man hat halt von aussen schon etwas vorgegeben, was man kapselt, und wo man für jedes Attribut oder jeden Schlüssel das gleiche machen muss. Da würde ich mir den Schreibaufwand da lauter Properties zu schreiben die bis auf die Namen identisch definiert sind, schon sparen wollen. Alternative wäre sich einen Dekorator für die Klasse zu schreiben der die Properties erstellt.

@Dennis89: Bei dem Ansatz von Sirius3 (den ich ja letztlich auch so in der Richtung angedeutet habe), wäre es problematisch Durck und Temperatur beim (reinen) Gas schon festzulegen. Das hätte ja zur Folge, dass man ein Gasgemisch erstellen kann das aus Gasen bei unterschiedlichen Druck- und Temperatur-Verhältnissen besteht.

Man sieht das ja an Deinem Beispiel wo Du diese Werte zweimal identisch angibst. Und da ”fehlt” dann in `GasMix` noch eine Validierung, dass diese Werte bei allen Komponenten gleich sind. Zudem hat der `GasMix` diese beiden Attribute nicht, das geisst `Gas` und `GasMix` sind nicht mehr so einfach austauschbar. Denn mit dem Beispiel von Sirius3 kann man ein `GasMix` aus jedem `AbstractGas` machen, auch aus `GasMix`-Objekten!

„Introspection“ bedeutet das man die Objekte zur Laufzeit untersuchen kann. Beispielsweise mit ``dir(some_object)`` sich eine Liste der Namen der Attribute geben lassen kann. `__getattr__()` wird aufgerufen wenn man ein Attribut abfragt welches das Objekt eigentlich nicht hat, also kann `dir()` das natürlich auch nicht ohne Hilfe ermitteln. Man kann `__dir__()` implementieren und da auch diese Namen mit zurückgeben, dann werden die bei `dir()` auch aufgelistet.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Erklärung.

Die Probleme die es mit sich bringt, wenn man Temperatur und Druck beim erstellen des reinen Gases gleich mit angibt, habe ich gar nicht bedacht. Das macht natürlich Sinn. Dann lasse ich das weg und geb die zwei Werte an, wenn ich die entsprechende Eigenschaft benötige. Also so wie ursprünglich von Sirius3 vorgeschlagen.

Es gibt auch noch eine Kühlwasser-Klasse 🫣 Aber erst über arbeite ich mal den Gasteil. 👍🏼


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

Hallo,

die Gas-Klassen habe ich erfolgreich eingebaut, dafür noch einmal vielen Dank.
Da wir hier beim Thema Klassen sind, hätte ich noch einmal eine Frage. Ob es für diesen Fall unbedingt notwendig ist weis ich nicht, aber ich würde meinen Code gerne mit einem Tool wie `Sphinx` dokumentieren. Es kann sicherlich nicht schaden und für mich hat es den Vorteil, dass ich den Umgang damit auch mal lerne. Die Frage ist nur, was schreibe ich denn so in die Docstrings. Habe mich mal etwas in fertigen Bibliotheken umgeschaut und das mal auf meine Kühler-Klasse angewendet. Schreibt man da noch mehr Beschreibung rein? Gehört in den Docstring der Klasse auch Informationen, was man mit der Klasse alles berechnen kann? Kann ich mich da auch an Faustformeln halten oder ist das eher nach belieben des Programmierers? Der Docstring in der Funktion ist ja ziemlich kurz, aber was gehört da noch rein? Was die Ringspaltfläche ist im Detail beschreiben? Eventuell die Formel in den Docstring mit aufnehmen? Das wäre doch vielleicht was, wenn man wissen will, was da abläuft.

Code: Alles auswählen

from functools import cached_property
from math import pi

from attrs import define, field


@define
class Cooler:
    """
    This class represent a generic pipe bundle cooler.

    :type number_of_pipes: int
    :param number_of_pipes: Set how many cooling-pipes the cooler should have.

    :type outside_diameter_cooling_pipe: int or float
    :param outside_diameter_cooling_pipe: Set the outside diameter of the cooling-pipes in m.

    :type inside_diameter_cooling_pipe: int or float
    :param inside_diameter_cooling_pipe: Set the inside diameter of the cooling-pipes in m.

    :type inside_diameter_outer_pipe: int or float
    :param inside_diameter_outer_pipe: Set the inside diameter of the water-pipe in m.

    :type water_temperature_in: int or float
    :param water_temperature_in: Set the cooling-water temperature before entering the cooler in K.

    :type water_temperature_out: int or float
    :param water_temperature_out: Set the cooling-water temperature after cooler in K.

    :type gas_temperature_in: int or float
    :param gas_temperature_in: Set the gas temperature before entering the cooler in K.

    :type gas_temperature_out: int or float
    :param gas_temperature_out: Set the gas temperature after cooler in K.

    :type cooling_counter_current: bool
    :param cooling_counter_current: If true, the direction of flow of the water is opposite to that of the gas.

    """

    number_of_pipes = field()
    outside_diameter_cooling_pipe = field()
    inside_diameter_cooling_pipe = field()
    inside_diameter_outer_pipe = field()
    water_temperature_in = field()
    water_temperature_out = field()
    gas_temperature_in = field()
    gas_temperature_out = field()
    cooling_counter_current = field()

    @cached_property
    def annular_gap_surface(self):
        """In m²"""
        return self.inside_diameter_outer_pipe**2 * pi / 4 - self.number_of_pipes * (
            self.outside_diameter_cooling_pipe**2 * pi / 4
        )
Wahrscheinlich ist die Frage sicherlich zu allgemein, aber ich dachte vielleicht könnt ihr mir grob ein paar Tipps geben. Vom Gefühl her soll ich da wahrscheinlich reinschreiben, was ich drin haben will oder mir als wichtig erscheint.

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

@Dennis89: Dass das eine Klasse ist gehört da nicht wirklich rein. Das bekommt der Leser und Sphinx ja schon durch das ``class …`` mit. „A generic pipe bundle cooler.“ wäre IMHO genug.

„Set …“ klingt nach einer Tätigkeit, aber das sind ja alles Attribute.

Das endet IMHO oft bei so etwas wie „:param number_of_pipes: Number of pipes.“ was keine Dokumentation ist die irgendwer braucht, weil das nicht wirklich was aussagt was man am Namen nicht ablesen kann.

Ist vielleicht eine Frage ob man mit der Materie vertraut ist oder nicht, aber ich persönlich würde hier eine Zeichnung praktisch finden, wo die Variablen eingezeichnet sind, statt viel Text der nicht wirklich mehr sagt als die Namen das bereits tun.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen und Danke für die Antwort.

Oh jetzt wähle ich sinnvolle Namen und beschreibe sie trotzdem noch, stimmt 😅
Dann kommt das weg und wird durch einen Hinweis ersetzt, dass die Werte in SI-Einheiten übergeben werden sollen. Eine kleine Zeichnung wäre für die die nicht wissen wie so ein Kühler aussieht, sicherlich hilfreich.
Manche können ja mit den Zeichen der Tastatur tolle Sachen skizzieren. Ich gehe davon aus, dass ich in `Sphinx` auch eine "normale" Zeichnung einfügen kann. Das schaue ich noch nach.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten