Attributänderung einer Klasse, die selbst ein Attribut einer anderen Klasse darstellt

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
Konse
User
Beiträge: 3
Registriert: Samstag 4. November 2023, 10:36

Hallo Zusammen,

ich bin neu hier und auch noch nicht so lange am programmieren in Python dran. Ich habe eine Frage und Ihr müsst bitte entschuldigen, falls meine Beschreibungen an der ein oder anderen Stelle nicht die korrekten Fachtermini enthalten. :?

Nun zur Frage:
Einmal angenommen ich habe eine Klasse Auto neben anderen Attributen hat dieses Auto das Attribut Rad. Rad ist wiederum selbst eine Klasse und besitzt das z.B. Attribut Reifen und Felge, dazu folgendes Skript:

Code: Alles auswählen

class Rad():
    def __init__(self, reifen, felge):
        self._reifen = reifen
        self._felge = felge
        
    @property 
    def reifen(self):
        return self._reifen
    
    @reifen.setter
    def reifen(self,neuer_reifen):
        self._reifen = neuer_reifen
        
class Auto():
    def __init__(self, rad, motor):
        self._rad = rad
        self._motor = motor
    
    def print_reifen(self):
        print(self._rad.reifen)
 
# Instanzierung des Rades: aVWCaddyRad
aVWCaddyRad = Rad("Sommerreifen", "Aluminiumfelge")
print(id(aVWCaddyRad))

# Instanzierung des Automodells: aVWCaddy
aVWCaddy = Auto(aVWCaddyRad,"V8-Motor")
aVWCaddy.print_reifen()

# Änderung des Attributes Reifen der Klasse Rad und Auswirkung auf die Klasse Auto
aVWCaddyRad.reifen = "Winterreifen"
print(id(aVWCaddyRad))
aVWCaddy.print_reifen()
Als Ausgabe erhalte ich:

2828178694160
Sommerreifen
2828178694160
Winterreifen


Bei der Initialisierung des Rads habe ich festgelegt, dass es sich bei der Bereifung um einen "Sommerreifen" handeln soll. Da nun der Winter kommt und die Räder auf den gleichen Felgen neue Reifen aufgezogen bekommt, andert sich das Attribut _reifen über den reifen.setter zu "Winterreifen". Wie man an der Ausgabe von der Methode print_reifen (in der Klasse Auto) sieht ändert sich damit auch automatisch das Attribut _rad des Objektes aVWCaddy.
Wie man anhand der Speicheradressen sieht ändert sich für das Objekt aVWCaddy die Speicheradresse bei Anderung des Attributes _rad nicht und damit zeigt das Objekt aVWCaddy immer noch auf das gleiche aVWCaddyRad. Ist es immer so für den Fall, dass ich Attribute einer Klasse verändere, dass gleichzeitig ein Attribut einer anderen Klasse darstellt?
Ich weiß, dass z.B. Integer Imutable sind, d.h. bei Zuweisung eines Zahlenobjektes zu einer Variable, die vorher auf ein anderes Zahlenobjekt zeigte, führt zur Änderung der Speicheradresse. Angenommen ein Attribut des Objektes aVWCaddy wäre _leistung, die als PS des Autos beschrieben wäre. Ändert sich die Leistung des Autos durch z.B. tuning, so ändert sich die Speicheradresse des Attributes _leistung. Allerdings ändert sich damit einhergehend nicht die Adresse des aVWCaddy, sondern eben nur der "Zeiger" auf das Attribut?
Kann hier auch eine Art "call-by-value" auftreten, wo eine Kopie erzeugt wird und sich das Attribut in der außenliegenden Klasse (in diesem Fall Auto) nicht ändert? Ich wäre sehr über ausführliche Erläuterungen dankbar, ich möchte besser verstehen was es mit dem Python spezifischen "Call by Object Reference" zu tun hat.

Viele Grüße und schönes Wochenende an alle! :wink:
Benutzeravatar
sparrow
User
Beiträge: 4198
Registriert: Freitag 17. April 2009, 10:28

Womit lernst du Pyhon?
Der Code sieht so unpythonisch aus, dass ich mir sorgen mache, dass das ein deutsschprachiges Buch ist.
Die Benennung der Variablen ist auch sehr untypisch und unnötig kompliziert. Namen schreibt man in Python klein_mit_unterstrich. Ausgenommen Klassen (PascalCase) und Konstanten (KOMPLETT_GROSS)

Die Setter sind überflüssig.
Den Code kann man einfacher so schreiben (ungetestet):

Code: Alles auswählen

class Rad():
    def __init__(self, reifen, felge):
        self.reifen = reifen
        self.felge = felge
        
class Auto():
    def __init__(self, rad, motor):
        self.rad = rad
        self.motor = motor
    
    def print_reifen(self):
        print(self.rad.reifen)
 
rad = Rad("Sommerreifen", "Aluminiumfelge")
print(id(rad))

caddy = Auto(rad, "V8-Motor")
caddy.print_reifen()

rad.reifen = "Winterreifen"
print(id(rad))
caddy.print_reifen()
Ob es sich bei der id um irgend eine Speicheradresse handelt, ist ein Implemtierunsdetail und daher nicht relevant. Da du sehr oft von einer Speicheradresse sprichst, bist du da womöglich auf einem Holzweg. Details findet man wie immer in der Dokumentation.
Grundsätzlich gilt: Die id eines Objektes ist während seiner Lebenszeit garantiert einzigartig und konstant.
Und eigentlich ist das auch die Antwort auf deine Frage.

Zu deinem konkreten Beispiel. Wenn du ein Objekt "caddy" hast und darin die Eigenschaft "reifen" und an "reifen" ein neues Objekt bindest, dann ist "caddy" natürlich noch immer das selbe Objekt wie vorher. Wäre auch ein bisschen seltsam, nur weil man etwas _in_ dem Objekt ändert, das ganze Objekt wegzuwerfen und ein neues zu nehmen.
Das kannst du sogar 1:1 in die reale Welt übertragen: Wenn du bei deinem Auto die Reifen wechselst, hast du noch immer das selbe Auto wie vorher - nur halt mit anderen Reifen.
Oder stell dir mal eine Liste vor (die ja ein Objekt ist) und die würde jedes Mal im Nirvana verschwinden, nur weil du ein Element anfügst.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Konse: Python ist „call by object sharing“, das heisst wenn man irgendwo ein Objekt übergibt, dann haben Aufrufer und Aufgerufener das *selbe* Objekt auf das sie zugreifen. Die teilen sich das. Und Zuweisungen an Namen oder Attribute weise *das Objekt* zu. Weder beim Aufruf noch bei einfachen Zuweisungen wird eine Kopie von einem Objekt erstellt.

Und in dem Absatz hier drüber kannst Du jedes vorkommen von „Objekt“ durch das Wort „Wert“ ersetzen. Das gilt in Python für „Wert“ und „Objekt“ „Wert“ und „Objekt“alle Werte, denn jeder Wert in Python ist ein Objekt.

Dieses Verhalten ist nicht Python-spezifisch in dem Sinn dass das nur bei Python so ist, oder das das nur bei Python und irgendwelchen exotischen Programmiersprachen so ist, das ist in vielen modernen, objektorientierten Sprachen so. Auch wenn einige neben Objekten noch andere Werte haben die sich etwas anders Verhalten. Bei Java beispielsweise die skalaren Grunddatentypen und Arrays, aber ansonsten verhält sich das bei Objekten genau so. JavaScript verhält sich auch so.

Dein Beitragstext ist ziemlich uneindeutig bis verwirrend, finde ich. „Ein Attribut ändert sich“ kann ja zwei Sachen bedeuten: Der Zustand des Attributs ändert sich, also der Wert der an den Attributnamen gebunden ist. Oder es wird ein anderer Wert an den Attributnamen gebunden, wodurch sich natürlich auch der Wert ändern kann. Aber nicht muss, weil es kann ja ein anderes Objekt aber mit dem gleichen Wert/Zustand an das Attribut/den Attributnamen gebunden worden sein.

Edit: Und nur noch mal speziell zum Betreff: Attribut einer Klasse wäre ein Attribut auf einer *Klasse*. Klassenattribute sind in dem Beispiel hier aber alles Methoden. Nur um das noch mal gesagt zu haben wenn wir hier Begrifflichkeiten sauber auseinanderhalten wollen. Klassen sind auch Objekte in Python. Wie alles was man an einen Namen binden kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Konse
User
Beiträge: 3
Registriert: Samstag 4. November 2023, 10:36

Hallo sparrow,

zunächst erstmal vielen Dank für deine Hinweise und die Antwort. Ich eigne mir Python über verschiedene Websites an, dass die Inhalte und Konventionen zum Teil nicht ganz konsitent sind habe ich leider auch schon bemerkt. Ich habe mich jetzt etwas darauf eingeschossen die Inhalte von "Real Python" zu verwenden und dort Dinge nachzulesen (Dabei sind mir natürlich auch noch Konventionen von anderen Seiten im Hinterkopf). Trotzdem bin ich sehr dankbar für konstruktive Kommentare. Deine Bemerkung zu Klassen- und Variablennamen habe ich mir ebenfalls angenommen und nochmal allgemein nachgelesen (https://www.delftstack.com/de/howto/pyt ... on-naming/). Danke hierfür !

Bezüglich der Getter und Setter bzw. der von mir verwendeten Properties bin ich wahrscheinlich etwas von C# voreingenommen. Dort habe ich die Datenkapselung (public, private, protected) als wichiges Konstrukt kennengelernt. So wie ich gelesen habe gibt es so etwas in Python nicht (obwohl es von verschieden Seiten behauptet wird). In C# hatte ich gelernt so gut es möglich ist jene Methoden und Attribute als private zu deklarieren und den Zugriff von außen über Getter und Setter zu ermöglichen. In Python gibt es wohl die Konvention private Attribute mit einem "_" als Präfix zu versehen. Das hat zwar keine Auswirkungen auf die Syntax sollte aber von verschiedenen Programmierenden annerkannt sein. Auch hatte ich in Foren gelesen, dass die Verwendung von Gettern und Settern in Python etwas umstritten ist, weil man diese aus den eben benannten gründen nicht zwangsläufig braucht. Trotzdem kann es manchmal wichtig sein, die Datenintegrität zu wahren z.B. wenn nur bestimmte Reifenmarken gesetzt werden dürfen und das setzen von "Herbstreifen" eine Fehlermeldung ausgeben sollte.

Es tut mir leid wenn ich etwas uneindeutig bin. Ich lerne noch, aber ich gebe mir Mühe alles was ich von euch Experten mitnehmen kann anzunehmen.

Viele Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Konse: Die Namenskonvention wird indirekt durch Programmierende auch von verschiedenen Werkzeugen anerkannt. IDEs oder Editoren die mit Plugins für die Python-Programmierung ausgerüstet sind, können zum Beispiel in vielen Fällen stellen wo von aussen auf Attribute zugegriffen wird die mit einem Unterstrich anfangen, diesen Zugriff mit einer Warnung markieren.

Wenn durch zuweisen eines illegalen Wertes eine Ausnahme ausgelöst werden soll, dann kann man das ja in Python durchaus mit einem Property lösen. Aber es macht in Python keinen Sinn schon vorsorglich ein Property anzulegen, welches nichts macht ausser den Wert jeweils ”durchzureichen” nur weil man sich die Möglichkeit offen lassen möchte das *vielleicht* später mal zu zu wollen. In manchen statisch kompilierten Sprachen würde man das machen, weil das für die Benutzer der Schnittstelle eine Änderung darstellt. Das heisst man müsste nach ändern von direktem Attributzugriff nach Property nicht nur diese Klasse neu kompilieren, sondern auch den Code der darauf zugreift. In Python ändert sich aber überhaupt nichts an der API. Der Code der auf das Attribut zugreift, macht das immer gleich, egal ob das ein direktes Attribut ist, ein Property, oder ”überhaupt nicht existiert” sondern über `__getattr__()` realisiert ist.

Ansonsten sind Datenkapselung und Zugriffsschutz zwar verwandte Themen, aber das ist nicht das gleiche. Wenn man durch Dokumentation/Namenskonventionen bestimmte Sachen zu Interna erklärt, ist das auch Datenkapselung. Und zwar auch richtig und echt. Man braucht nur einen deutlich sichtbaren halbhohen Zaun und ein „Privatgrundstück, nicht betreten, sonst Konsequenzen tragen“-Schild. Keinen zwei Meter-Zaun der unter Strom steht, mit Nato-Stacheldraht garniert, und ein Rudel Kampfhunde. 🙂
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Konse
User
Beiträge: 3
Registriert: Samstag 4. November 2023, 10:36

@__blackjack__: Danke für deine beiden Nachrichten und sorry für meine teilweise verwirrenden Sätze, wie gesagt: ich lerne noch.
Für mich nochmal kurz zusammengefasst: i.A. setzt man in Python erstmal das was geht als public. Das impliziert jetzt für mich, dass ich alle Attribute, die nur Instanzintern gebraucht werden als private deklariere und auf alles was ich von außen zugreifen muss als public? Sollte mir später einfallen, dass ich z.B. Einschränkungen von Wertbelegungen vorgeben möchte, dann kann ich dies in Python mithilfe von properties machen. Ein einfaches durchreichen der Attribute über einen getter ist in Python eher nicht typisch, wenn ich das richtig herausgelesen habe?
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Also ich mache so gut wie nie irgendwelche expliziten "Markierungen", dass etwas privat ist. Normalerweise kann man in meinem Code die allermeisten Bestandteile benutzen, ohne das was kaputt geht. Wie sinnig die Nutzung kleinster Helfer ist, kommt natürlich auf den Anwendungsfall an.

Falls es darum geht, dass die API nicht zu sehr aufgebläht wirkt, so muss man ja nicht zwingend jede Methode in die Doku aufnehmen. Lese bei Python-Code von anderen Leuten auch häufiger, wenn was geändert oder weggenommen werden soll, den Ausdruck "was never documented". Diese Teile waren also nutzbar, jedoch ohne Garantie auf Kompatibilität zu anderen Versionen. Quasi so "halb privat".
Antworten