Klassen: Unterschiedliche Methoden für gleiche 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.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

Hallo Leute,

ich möchte nach dem "DRY" Motto meinen Code so allgemein und kurz wie möglich schreiben.
Prinzipiell geht es um verschieden geometrische Körper (z.B. Kugel und Wuerfel).
Grundlegende Eigenschaften sind z.B. Position und Orientierung und geometrische Parameter (Radius bei Kugel, Kantenlänge bei Wuerfel usw.).
Für die Bestimmung des Abstandes muss für jeden entsprechenden Körper eine extra Funktion geschrieben werden. Diese Funktionen habe ich momentan in unterschiedlichen Modulen untergebracht.
Mein formaler Beispielcode sieht folgendermaßen aus:

Code: Alles auswählen

import numpy as np

class Koerper():
    def __init__(self,pos=np.array([0.,0.,0.]),ori=np.array([1.,1.,1.])):
        self.pos          = pos
        self.ori          = ori
        self.koerper_para = []
    
    def Abstand(self,Koerper_library,Koerper):
        return Koerper_library.Abstand(self,Koerper)

import Wuerfel

Wuerfel_A = Koerper(pos=np.array([1.,2.,3.]),ori=np.array([1.,0.,2.]))
Wuerfel_A.koerper_para=2. # KANTENLÄNGE

Wuerfel_B = Koerper(pos=np.array([-10.,-20.,-33.]),ori=np.array([3.,0.,1.]))
Wuerfel_B.koerper_para=1. # KANTENLÄNGE

print Wuerfel_A.Abstand(Wuerfel,Wuerfel_B)
Dabei ist die Abstands-Methode im Modul Wuerfel zu finden.
Meine Frage ist jetzt, ob es da noch eine elegantere Weise gibt, also eine pythonische Art?
Die Methoden sind kompliziert und sollen für jeden geometrischen Körper in einer extra Datei geschrieben werden. Die Abstrakte Klasse Koerper wiederum ist Teil des Hauptprogramms.
Vielen Dank schon mal im Voraus.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@schneitzmaster: Was Du eigentlich suchst ist Vererbung. "Koerper" ist ja nur eine abstrakte Oberklasse, die selbst nicht den Abstand berechnen kann. Erst konkrete Ableitungen der Klasse "Koerper", also "Wuerfel" oder "Kugel" implementieren dann eine Methode Abstand. Somit weiß die Instanz schon, welcher Art Koerper sie entspricht und man muss das nicht extra noch der Abstands-Methode sagen. Wobei ich nicht verstehe, wie Du bei Deinem Interface den Abstand zwischen Würfel und Kugel berechnen willst.

Noch eine Warnung: man sollte nicht veränderbare Objekte als Default-Parameter verwenden.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

Hi Sirius,

an dem Abstand zwischen Würfel und Kugel bin ich momentan noch nicht interessiert.
Wie würdest du das Programm technisch Umsetzen? Momentan habe ich ja das Modul "Wuerfel". Müsste ich in diesem noch einmal eine Würfelklasse erstellen die als Parent die Klasse "Koerper" hat?
Wenn dem so ist gibt es doch ein Problem, da die "Koerper"-Klasse ja im Hauptprogramm steht bzw. die Wuerfelklasse in dem eigenen Modul nie ohne das Hauptprogramm aufgerufen werden kann oder?

Was meinst du mit nicht veränderbare Objekte (welche sind das in meinem Beispiel)? Welche Objekte sind bei mir nicht veränderbar und sollten default sein?
BlackJack

@schneitzmaster: Die Basisklasse müsste dann in ihr eigenes Modul damit kein zirkulärer Import entsteht der Probleme machen wird.

Es geht bei den Defaultwerten um `pos` und `ori`, die sind *veränderbar*! Und das ist potentiell gefährlich weil diese Arrays genau *einmal* erstellt werden wenn die Methode definiert wird und dann von *allen* Exemplaren geteilt werden. Wenn man daran also etwas verändert, wirkt sich das auf *alle* Exemplare aus.
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@schneitzmaster: Für zwei Dimensionen würde ich so anfangen: http://www.python-forum.de/pastebin.php?mode=view&s=428

Falls Du etwas erklärt haben möchtest, gib Bescheid.
In specifications, Murphy's Law supersedes Ohm's.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@BlackJack: Ach so das war mir nicht bewußt. Ich habe diese Argumente wie die Argumente von ganz normalen Funktionen betrachtet. Dort finde ich das Konzept mit den default-Argumenten vorteilhaft, da man im Code sofort erkennt, was die Argumente bedeuten. Gibt es so eine Möglichkeit auch für Methoden? Weil man sonst besonders bei dem Beispiel leicht mal 'pos' und 'ori' vertauschen könnte.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@schneitzmaster: das Verhalten ist auch genau gleich wie bei ganz normalen Funktionen. Und ich glaube, Du verwechselst gerade Keyword-Argumente mit Defaultwerten von Argumenten.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@Sirius3: Ja genau ich will Keyword-Argumente verwenden. Mir war bis gerade nicht der Unterschied zwischen Keyword und Default bewußt da ich in meinen bisherigen Programmen keinen Unterschied gemerkt habe. Allerdings habe ich da meistens mit Funtionen und nicht mit Methoden gearbeitet.

@pillmuncher: Vielen Dank für dein Beispiel. So ähnlich will ich mein Programm auch strukturieren. Allerdings sollen die einzelnen Shapes in unterschiedlichen Dateien gespeichert werden. Ist es dann gut praxis selbige als Module aufzurufen?
Konkret würde es anhand deines Beispiels die Datei 'Shape_basis.py', die Datei 'Rectangle.py' und 'main.py' geben.
Wäre eine korrekte Implementierung dann wie folgt:

Code: Alles auswählen

import math
class Shape:
...

Code: Alles auswählen

import shape_basis
class Rectangle(shape_basis.Shape):
    def __init__(self, x1, y1, x2, y2):
...

Code: Alles auswählen

import rectangle

rectangle_A = rectangle.Rectangle(x1=1,x2=2,y1=1,y2=3)
# do stuff with rectangle_A
BlackJack

@schneitzmaster: Illustration des Problems:

Code: Alles auswählen

In [3]: k_a = Koerper()

In [4]: k_b = Koerper()

In [5]: k_a.pos[0] = 42

In [6]: k_a.pos
Out[6]: array([ 42.,   0.,   0.])

In [7]: k_b.pos
Out[7]: array([ 42.,   0.,   0.])

In [8]: k_a.pos is k_b.pos
Out[8]: True
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@BlackJack: ja das war mir schon klar. In meinen bisherigen Programmen habe ich das allerdings immer so verwendet:

Code: Alles auswählen

k_a = Koerper(pos=np.array([0.,0.,0.]),ori=np.array([1.,1.,1.]))

k_b = Koerper(pos=np.array([0.,0.,0.]),ori=np.array([1.,1.,1.]))
damit klappt es. Allerdings gebe ich dir Recht, dass bei einem Auslassen der Neudefinition der Defaultwerte es zu Problemen kommt. Irgendwo hatte ich einmal gelesen, dass es besser ist mit Default-Werten zu Arbeiten, da man dann sofort sieht was als Argument gebraucht wird. Das war dann wohl murks
BlackJack

@schneitzmaster: Dafür ist ja eigentlich Dokumentation da um die API zu beschreiben.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@BlackJack: Mhh ja klar das stimmt. Ich empfand es aber als komfortabler gleich der Funktion anzusehen was genau gebraucht wird. Aber klar wenn es dann solche Probleme entstehen. Eine Frage stellt sich mir dann aber. Warum sollte man dann überhaupt Default-Argumente verwenden. Die könnte man doch dann auch gleich fest setzen. Also zumindest bei Methoden.
BlackJack

@schneitzmaster: Man hat manchmal Funktionen oder Methoden bei denen im Regelfall immer der gleiche Wert verwendet werden würde, und damit der Benutzer den nicht jedes mal angeben muss aber etwas anderes angeben kann, wenn er will, gibt es Default-Werte. Und manchmal gibt es auch sozusagen sinnvolle ”Nullwerte”. Wie in Deinem Beispiel.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

okay danke. damit ist mein Problem vorerst gelöst obwohl das doch ziemlich umständlich ist 3 Dateien zu haben. Geht aber wohl nicht anders.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@schneitzmaster: Du willst doch die drei Dateien haben. Natürlich kannst Du auch alles in eine Datei schreiben.
BlackJack

@schneitzmaster: Man könnte auch alles in eine schreiben. Oder in zwei.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

ja klar will ich die drei Dateien haben. aber in der einen steht dann halt nur die "Shape" Klasse. Ausserdem sind die Module für Wuerfel, Kugel usw. nicht autonom. Andersherum würde ich das "DRY" motto verletzen wenn ich die shape klasse in die module mit rein schreibe...na ja muss ich wohl so umständlich machen
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

@ pillmuncher: Warum hast du in der Shape-Klasse überall mit "@property" und nicht mit "def __init__(self)" gearbeitet?
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@schneitzmaster: Die property-Methoden in Shape, die keinen NotImplementedError schmeißen, stellen eine default-Implementierung dar, die von den abgeleiteten Klassen überschrieben werden können, die anderen müssen überschrieben werden, da in Shape keinerlei gerechtfertigten Annahmen darüber getroffen werden können, wie irgendeine konkrete Implementierung aussehen könnte. Man könnte es natürlich auch anders machen, aber so gefällt es mir besser, weil man beim Ableiten gleich sieht, was man implementieren muss.
In specifications, Murphy's Law supersedes Ohm's.
schneitzmaster
User
Beiträge: 94
Registriert: Freitag 26. Oktober 2012, 15:35
Wohnort: Hamburg

okay super danke.
Eine letzte Frage für heute habe ich noch. Ist es möglich in der Shape-Klasse beim initialisieren etwas zu übergeben?
In etwa so:

Code: Alles auswählen

import numpy as np
class Shape(object):
    def __init__(self,dim):
        self.dim    = dim
        self.foo    = []
    def do_general_stuff(self):
        if self.dim==3: # do stuff for only 3D
            self.foo = np.array([1.,1.,1.])
        elif self.dim==2: # do stuff for only 2D
            self.foo = np.array([1.,1.])

class Wuerfel(Shape):
    def __init__(self, kante):
        self.kante   = kante
        self.mid      = np.array( [0.,0.])
    def do_specific_stuff(self):
        self.do_general_stuff()
        self.mid = self.kante*self.foo

Wuerfel_A = Wuerfel(kante=2.,dim=3)

Wuerfel_A.do_specific_stuff()
print Wuerfel_A.foo
Zuletzt geändert von schneitzmaster am Samstag 4. April 2015, 20:09, insgesamt 2-mal geändert.
Antworten