@Dennis89: Mir ist nicht recht klar was du tun möchtest, was die Ausgangsdaten sind, und was das Ergebnis sein soll.
Du schreibst, dass du die Ausgangsdaten als JSON bekommst. Ist das eine Beschreibung der Maschine als ganzer, inklusive aller Komponenten? Oder eine Beschreibung der Komponenten als solcher, die du dann zur gewünschten Maschine zusammenbauen must? Du schreibst auch, dass in eine Maschine verschiedene Zylinder eingebaut werden können. Sind das verschiedene Zylinder, die als Alternativen zueinander eingebaut werden können, oder kann eine Maschine mehrere Zylinder gleichzeitig haben? Du schreibst auch, dass die Zylinder ebenfalls aus Komponenten bestehen, die dann einen Einfluss auf das Gesamtergebnis haben. Ist das orthogonal zur Maschine? Damit meine ich: ist es für die Maschine geometrisch egal, aus welchen Komponenten die Zylinder bestehen, oder führt das zusätzliche Beschränkungen mit sich, also dass ein Zylinder mit dan Maßen
uvw nur eingebaut werden kann, wenn er die komponenten
ABC hat, aber nicht die Komponenten
DEF? Das würde bedeuten, dass du Validierungen durchführen musst. Die einfachste Validierung ist: Habe ich alle benötigten Daten zur Durchführung meiner Bereschnungen? Oder komplexere Validierungen wie: Sind die Eingangsdaten miteinander kompatibel, oder bekomme ich Werte die nicht passen, etwa Zylindermaße, die gar nicht auf die Maschine passen? Oder kannst du das einfach ignorieren, weil der Erzeuger der Daten dafür zuständig ist?
Die Idee, zuerst einmal die Komponentenklassen mittels
cattrs zu erzeugen, halte ich für richtig. Danach stellt sich aber die Frage: Sind das die Klassen, die auch das Endergebnis bescheiben, oder sind die mit
cattrs erzeugten Klassen eine intermediäre Representation der Rohdaten, die man dann zur Konstruktion der eigentlichen Komponenten verwendet? Muss die Gesamtmaschine alle geometrischnen Eigenschaften beinhalten, also nicht nur die Maße der Komponenten, sondern auch deren geometrische Position zueinander? Anhand dessen was du bisher geschrieben hast, brauchst du das anscheinend nicht, sondern die Gesamtmaschine ist nur ein Berechnungsobjekt, ähnlich einem Spreadsheet. Damit stellt sich die Frage: Muss diese Gesamtmaschine wirklich die Maschinen- und Komponentenstruktur 1:1 widerspiegeln, oder kann man sie tatsächlich als Spreadsheet betrachten, das aus Repräsentation von Berechnungen besteht?
Generell kann man das auch als Algebra betrachten. Eine Gesamtmaschine besteht aus verschiedenen Komponenten, wir zB. Zylindern. Wenn jede Maschine genau einen Zylinder hat, der verschiedene Maße haben kann, dann hat die diese Struktur:
Maschine ⨯ Zylinder. Hat sie zwei Zylinder gleichzeitig, hat sie diese Struktur:
Maschine ⨯ Zylinder ⨯ Zylinder. Müssen die Zylinder dieselben Maße und Subkomponenten haben, dann hat man:
Maschine ⨯ (2 * Zylinder).
Das lässt verschiedene Implementationen zu. Etwa:
Code: Alles auswählen
from typing import Self
class MachineCalculator:
...
def add_cylinder(self, cylinder:Cylinder) -> Self:
...
def add_XYZ(self, xyz:XYZ) -> Self:
...
...
calculator = MaschineCalculator().add_cylinder(cylinder).add_XYZ(xyz)
result = calculator.calculate_something()
Oder auch:
Code: Alles auswählen
class MachineCalculator:
...
def __add__(self, component:Component) -> Self:
match component:
case Cylinder(height=height, diameter=diameter):
...
case XYZ(x=x, y=y, z=z):
...
...
calculator = MaschineCalculator() + cylinder + xyz
result = calculator.calculate_something()
Oder, falls die Maschine mehrere Zylinder haben kann, die dieselbem Maße haben müssen:
Code: Alles auswählen
from toolz.functoolz import flip
class Cylinder:
...
def __mult__(self, n:int): tuple[Self]:
return (self,) * n
__rmult__ = flip(__mult__)
...
class MachineCalculator:
...
def __add__(self, component:Component|tuple[Component]) -> Self:
match component:
case Component() as component, *components
if not components:
return self + component
return self + component + components
...
...
calculator = MaschineCalculator() + 2 * cylinder + xyz
result = calculator.calculate_something()
Die type annotations hier dienen zur Illustration, ich sage nicht, dass du sie verwenden musst.
Die Ergebnisse der Methoden in
MachineCalculator oben sind alle vom Typ
Self, also
MachineCalculator. Das lässt zwei Implementationen zu: Enweder
self wird mutiert, oder es wird eine mutierte Kopie zurückgegeben. Letzteres ist idR. die übersichtlichere Variante und angesichts der geringen Datenmengen hier vorzuziehen.
Mein Punkt hier ist dieser: Zuerst muss man sich die Struktur des Problems bewusst machen und eine angemessene Form finden, diese im Code auszudrücken. Die Struktur ist in deinem Fall eher algebraisch als geometrisch. Du willst ja kein CAD Programm bauen, sondern möchtest Berechnungen durchführen. Um die Performance brauchst du dich erst zu kümmern, wenn das ausprogrammiert ist und korrekt arbeitet. Wie Kent Beck immer sagt:
First make it work. Then make it right. Then make it fast.
In specifications, Murphy's Law supersedes Ohm's.