Zugriff auf Variablen aus einem Objekt auf andere Objekte

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
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

Guten Morgen zusammen,
ich versuche mich gerade als Python-Lehrling mit der objektorientierten Programmierung und frage mich, wie man (im Sinne eines sauberen Programmierstils), eine private Variable eines Ziel-Objektes mit dem Inhalt einer privaten Variablen eines anderen (Quell-)Objektes belegen kann.

Also herausgefunden habe ich, dass man da wohl mit sog. Getter/Setter arbeiten kann und sollte, nur was, wenn ich die Variable bereits innerhalb der Initialisierung (also innhalb von "__init__" belegen möchte).
Denn dann habe ich das Problem, dass dem Zielobjekt, das andere Objekt nicht bekannt ist. Sollte ich das Quellobjekt dem Zielobjekt als Parameter übergeben oder eher den Umweg über globale Variablen wählen?

Nochmal zur Verdeutlichung, wie ich mir das vorstelle:

Code: Alles auswählen

class Quelle(object):
    def __init__(self):
        self.__value = 7
    def GetValue()
        return(self.__value)

class Ziel(object, <Instanz der Quelle>):
    def __init__(self):
        self.__variable = <Intanz der Quelle>.GetValue()
Wenn ich dann eine Vielzahl von Instanzen zu übergeben hätte würde die Parameterliste ja doch recht lange werden.

Hättet Ihr dazu einen guten Vorschlag, wie man das i.d.R. sauber programmiert?

Vielen Dank für Eure Unterstützung!

stielchen
Zuletzt geändert von Anonymous am Sonntag 27. Juli 2014, 10:24, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Du kannst über den Punktoperator sehr einfach auf den Namensraum eines Objektes zugreifen:

Code: Alles auswählen

class AnyClass(object):
    def __init__(self, value):
        self.value = any_value

class AnotherClass(object):
    def __init__(self, value):
        self.value = value

Code: Alles auswählen

>>> any = AnyClass('any')
>>> another = AnotherClass('another')
>>> any.value
'any'
>>> another.value
'another'
>>> any.value = another.value
>>> any.value
'another'
>>> another.value
'any'
Natürlich kannst Du das eine Objekt auch an ein anderes übergeben:

Code: Alles auswählen

>>> any = AnyClass('any')
>>> another = AnotherClass(any.value)
>>> any.value
'any'
>>> another.value
'any'
Dass es globale Variablen gibt, vergisst Du am besten wieder. In sich geschlossene Namensräume mögen am Anfang umständlich erscheinen, sind aber die beste Erfindung seit der Mondlandung, da sie Dir von vorneherein Ordnung in Deinen Code bringen. Jeder Name hat seine Bedeutung und Existenz immer (nur) dort, wo er auch hingehört. Und zwischen den Namensräumen kann ich Werte ja beliebig hin und her bewegen, anstatt alles auf einem großen unübersichtlichen Haufen zu haben.

Wenn Du sehr viele Werte an ein Objekt zu übergeben hast, dann bedienst Du Dich einfach einer Liste, Tuple, Dictionary etceterapepe.

Code: Alles auswählen

class AnyClass(object):
    def __init__(values):
        self.values = values

Code: Alles auswählen

>>> any = AnyClass([0, 1, 2, 3, 4, 5, 6])
>>> any.values[2:5]
[2, 3, 4]
Oder so:

Code: Alles auswählen

class AnyClass(object):
    def __init__(self, *values, **kwvalues):
        self.values = values
        self.kwvalues = kwvalues

Code: Alles auswählen

>>> any = AnyClass(0, 1, 2, 3, 4, five=5, six=6)
>>> any.values[0]
0
>>> any.kwvalues['five']
5
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo stielchen,
sogenannte explizite Getter/Setter sind unnötig. In Python gibt es einen viel direkteren Weg, um an Attribute einer Instanz heranzukommen: 'object.value'. In diesem Zusammenhang gibt es eine Konvention, dass Attribute ohne '_' öffentlich, welche mit '_' nicht öffentlich sind. Zwei '__' am Anfang eines Attributs braucht man nur, wenn man eine zu komplexe Klassenhierarchie aufgebaut hat, dass man sie selbst nicht mehr versteht, also am besten nie. Attribute mit zwei '__' vorne und hinten haben für Python eine besondere Bedeutung, sollten also nicht für eigene Namen benutzt werden.

Zum eigentlichen Problem: alle Abhängigkeiten von einer Instanz mit anderen Instanzen müssen explizit vorgenommen werden, also indem man z.B. die Quell-Instanz dem Ziel beim Erzeugen mitgibt:

Code: Alles auswählen

class Quelle(object):
    def __init__(self):
        self.value = 7

class Ziel(object):
    def __init__(self, quelle):
        self.variable = quelle.value

def irgendwas():
    quelle = Quelle()
    ziel = Ziel(quelle)
Hier stelle ich mir aber die Frage, warum braucht "Ziel" überhaupt "quelle". Entweder braucht Ziel nur den Wert von 'quelle.value', dann kann ich ihn auch gleich direkt übergeben:

Code: Alles auswählen

class Ziel(object):
    def __init__(self, variable):
        self.variable = variable

def irgendwas():
    quelle = Quelle()
    ziel = Ziel(quelle.value)
oder 'Ziel' braucht eine Quelle-Instanz, dann muss ich aber die ganze Instanz speichern und muss den internen Aufbau von Quelle kennen.

Wird Deine Parameterliste recht lang, solltest Du Dein Programm umstrukturieren; ich kann mich nicht erinnern, jemals mehr als 5 Parameter gebraucht zu haben.
BlackJack

Ergänzend zum bisher gesagten, Wenn ein Objekt einen Wert aus der nichtöffentlichen API eines anderen Objekts benötigt, dann ist das ein „code smell”. Das kann manchmal nötig sein, ist aber auf jeden Fall etwas wo man überlegen sollte ob das Attribut in der Quelle dann öffentlich sein sollte, denn wenn es das nicht ist, sollte es ja niemand von aussen benötigen.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

@all:
Ich danke Euch allen für die Unterstützung und die schnelle Rückmeldung.

@BlackJack:
Den Begriff "code smell" kannte ich bislang noch nicht, subsumierte das bislang unter den Begriff "schlechter Programmierstil".
Habe den Begriff mal auf Wikipedia nachgelesen und die Ausführungen dort haben mir sehr geholfen um noch einmal über meinen Ansatz nachzudenken.

Ich war bislang gedanklich im Getter/Setter-Ansatz verhaftet, um den Zugriff auf Instanzvariablen nur über eine Methode zu gestatten.
Deswegen wollte ich diese Variablen so "geheim" wie möglich halten, damit man nicht auf direktem Wege darauf zugreifen kann, sondern gezwungen
wird die Methode zu benutzen. Hatte jetzt keinen tieferen Sinn, sondern sollte halt nur einem (vermeintlich) "guten Stil" dienen.

@Sirius3
Also könnte man auf die "__" (2 Unterstriche) vollkommen verzichten? Ich dachte bislang, das wäre eine prima Sache eine gewisse Vertraulichkeit zu schaffen.
Allerdings stellt sich mir nun die Frage, warum überhaupt, da ICH doch selbst das Programm schreibe, also vor wem habe ich denn was bei der Nutzung dieser Art
von Instanzvariablen zu verheimlichen? Es ist auch nicht geplant, das Endprodukt irgendwie anderen Programmen zum Import zur Verfügung zu stellen.
Vielleicht könnte man unter diesem Gedanken ja auf alle "__" und "_" verzichten und nur "normale" öffentlich bekannte Variablen verwenden.
Was meinst Du denn mit einer "zu komplexe Klassenhierarchie"? Ist denn die Verwendung von "__" nur in diesem Zusammenhang zu sehen?
Zu Deiner Frage "Hier stelle ich mir aber die Frage, warum braucht "Ziel" überhaupt "quelle". Entweder braucht Ziel nur den Wert von 'quelle.value', dann kann ich ihn auch gleich direkt übergeben:"
Es sind in der Tat mehrere Variablen, daher finde ich Deinen Ansatz besser gleich das ganze Objekt mitzugeben, anstatt eine lange Liste von Attributen dieser speziellen Instanz.

Aber vielleicht noch etwas konkreter um den Kontext besser zu verstehen:
Ich möchte in VirtualBox 2 VM's bedienen, jede wird über ein Pyhton-Objekt (z.B. VM1 und VM2) gesteuert mit z.B. VM1.Start oder VM2.Reboot etc. (geht übrigens mit dem vboxmanage-Kommando).
Nun hat jede dieser VM's z.B. eine andere IP-Adresse, Hostnamen etc., diese Informationen hätte ich natürlich über den globalen Ansatz allen VM's zur Verfügung stellen können, aber das wollte ich
dann im Sinne eines besseren Programmierstils nicht machen.

In diesem Sinne dachte ich mir, dass diese Informationen ja eher zum Objekt selbt gehören, nur benötigt die VM1 in meinem Vorhaben auch die IP der VM2, da diese miteinander interagieren.
Da hatte ich mir gedacht, dass ich bei der Initialisierung der VM1 dort eine interne Variable mit der IP-Adresse von VM2 einrichte. Glaube nach eueren Ausführung, dass es besser wäre, der VM1 das Objekt VM2 über
einen Parameter mitzugeben. Ich empfand das halt irgendwie als etwas zu "sperrig", auch in Hinblick auf die Ideen die mir evtl. sonst noch in Zukunft kommen. Ich vermutete, dass man im Python aus einer Funktion heraus
auf globale Variablen zugreifen kann, hab das doch irgendwo mal gelesen. Kann man denn nicht auch innerhalb von VM2 auf eine öffentliche Methode von VM1 zugreifen? Das würde mir ja am besten gefallen.

Ich bin auch noch am überlegen, wie ich mit einem gemeinsamen Gateway umgehe. Im Moment habe ich dafür eine neue Klasse "Gateway" geschrieben, aber lt. Wikipedia zum Thema "code smell"
ist das wohl eine "faule Klasse", da sie ja nicht wirklich viel tut, außer eine IP-Adresse und sonstige Werte zur Verfügung zu stellen.

@mutetella:
Wo machen denn globale Variablen überhaupt Sinn? Ich meine, wenn man z.B. eine Information wirklich "überall" im Code benötigt wäre es doch etwas umständlich, diesen
Wert immer via Parameter den einzelnen Instanzen zur Verfügung zu stellen. Wie würde denn Dein Ansatz bei den o.g. Gateway-Informationen aussehen?
Oder packe ich die "globalen" Informationen in Deine erwähnte Liste und übergebe diese immer den Instanzen?
BlackJack

@stielchen: „Code smell” ist nicht automatisch schlechter Stil, denn manchmal können solche Sachen, die auf den ersten Blick „komisch riechen” ja tatsächlich die angemessene Lösung für etwas sein. „Guter Stil” wäre in dem Fall nur ein umschreiben der Stelle ohne dass dadurch etwas gewonnen wird. Bei Deinem Fall kann es ja durchaus sein, dass zwei Klassen semantisch so eng zusammen gehören, dass ein Exemplar der einen direkt auf Interna der anderen zugreifen muss. Da aus Stilgründen einen Getter zu schreiben, ändert daran ja nichts, ausser dass dann plötzlich etwas an der öffentlichen API aufgetaucht wäre, was dort eigentlich nicht sein sollte, weil *andere* Objekte eben nicht auf das interne Datum zugreifen sollten, dann aber eine Methode dafür hätten.

Ein führender Unterstrich ist in Python das Zeichen für ”geheim”. Und wenn man sowohl Getter als auch Setter für ein ”geheimes” Attribut anbietet, dann ist es letztendlich nicht mehr geheim und man kann auch direkt darauf zugreifen. Bleibt die Frage wie sich das bei „read only”-Attributen verhält. Ich neige dazu das einfach zu dokumentieren oder gar einfach auf den gesunden Menschenverstand zu setzen.

Die zwei führenden Unterstriche verändern hinter den Kulissen den Namen von dem Attribut, so dass der Klassenname mit darin enthalten ist. Also kann man entweder in einer sehr tiefen Klassenhierarchie auf mehreren Ebenen den ”gleichen” Namen für unterschiedliche Dinge verwenden, oder bei Mehrfachvererbung von Klassen erben, die ”gleiche” Attributnamen verwenden. In solche Situationen kommt man aber so gut wie nie, weil in Python in der Praxis in der Regel weder grosse Vererbungshierarchien noch Mehrfachvererbung vorkommt.

Natürlich kann man von `vm2` heraus auf eine öffentliche Methode von `vm1` zugreifen, allerdings muss `vm2` dazu ja `vm1` kennen. Ich habe mich hier mal an PEP8 bei der Namensschreibweise gehalten um Deutlich zu machen dass es sich bei `vm1` und `vm2` um Exemplare handelt und nicht um die Klasse(n).

Globale Variablen machen bei tatsächlich globalem Zustand Sinn. Je nach dem wen man fragt, kann die Antwort also auch „Niemals.” lauten. ;-)
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

Ich habe das ganze jetzt über eine Parameterübergabe realisiert. Jedoch habe ich dabei nicht das ganze Objekt übergeben, sondern nur das Attribut, welches ich eben benötige. Ich denke die Anzahl der Werte die ich in der jeweiligen VM benötige wird sich in Grenzen halten, ansonsten packe ich das der Überschaubarkeit wegen in eine Liste.

@BlackJack:
Übrigens ein guter Hinweis von Dir, diesen PEP8 werde ich mir mal genauer anschauen. Gibt es eigentlich noch weitere lesenswerte, bzw. wichtige PEP's? Doch hoffentlich nicht alle?

Danke Euch nochmal für die Hilfe!
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@stielchen: Listen als Parameterersatz zu mißbrauchen ist keine gute Idee; zu magisch, fehleranfällig, unübersichtlich und änderungshemmend.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

@Sirius:
Hm, wie würdest Du denn dann eine größere Parameterliste übergeben? Ich meine, wenn es denn mal notwendig sein sollte und man auf den Einsatz von globalen Variablen verzichtet. Ich glaube in C gibt es z. B. die Option eine Struktur als Parameter zu übergeben.

In meinem Vorhaben sind es (bislang) zum Glück nicht viele, daher ist es unproblematisch, ich frage nur generell.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@stielchen
Ich glaube, da musst Du erstmal unterscheiden, ob es sich um einen Parameter handelt, der eben aus vielen Werten besteht. Da ist eine Liste sicher nicht verkehrt. Wenn allerdings eine Funktion sehr viele verschiedene Parameter benötigt, dann kann das oft ein Zeichen dafür sein, dass man an der Struktur insgesamt etwas ändern sollte. Was, muss man dann natürlich im einzelnen sehen.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@stielchen: Für das was man in C mit Strukturen löst, verwendet man in Python Klassen/Datentypen. Das sind halt nicht nur die Felder einer Struktur sondern auch noch die Funktionen die zu dieser Struktur gehören in Form von Methoden. Aber man kann halt auch Klassen haben die vorrangig als Verbund von zusammengehörigen Werten fungieren. Wenn die Werte nicht ausgetauscht oder neu zugewiesen werden müssen und keine Methoden sinnvoll sind, eignet sich dafür auch `collections.namedtuple()` um Datentypen dafür zu erzeugen.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

@mutetella:
Ja, da gebe ich Dir Recht, dann sollte man nochmal grundsätzlich seinen Ansatz überdenken. Aber zum Glück ist es bislang nur ein Wert, den ich in einem Objekt von einem anderen benötige.

@BlackJack:
Danke für den Hinweis, ich schaue mir das mit den "collections.namedtupe()" bei Gelegenheit mal näher an.
Antworten