__init__ ein Objekt (eine Instanz) übergeben

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
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

Hallo zusammen,

vorab_1: ich bin neu hier und habe schon noch meiner Frage gesucht, aber keine Antwort gefunden. Falls sie bereits beantwortet wurde, freue ich mich auch über einen Link.
vorab_2: Python ist meine erste Programmiersprache, die ich zur Zeit lerne. Bisher habe ich ein Buch zur Einführung und diverse Videos bei Youtube zum Lernen verwendet. Ich verfüge also über keine wirkliche Erfahrung was das Programmieren allgemein angeht, bilde mir aber ein, dass ich die wesentlichen Basics aus dem Buch für Python3 verstanden habe.

Nun zu meiner Frage zur OOP, bei der es nicht um Vererbung geht (was Vererbung macht, ist mir relativ klar):
Meine Überlegung: ich möchte ein Auto mittels einer Klasse Fahrzeug erstellen und dieses Auto soll 4 Räder haben, welche ebenfalls mittels einer Klasse Rad erstellt werden. Der Code könnte z.B. wie folgt aussehen:

Code: Alles auswählen

class Rad():
    def __init__(self, diameter, mass):   
        """Einheiten: [mm],[kg]"""
        self.durchmesser=diameter
        self.masse=mass

vorderrad=Rad(700,10)
hinterrad=Rad(750,14)

#alle Attribute von einer Instanz 'Rad' mitsamt deren Werte ausgeben
print(vars(vorderrad))
print(vars(hinterrad))

class Fahrzeug():
    def __init__(self, lenght, width, hight, lf, rf, lb, rb):         # lf-->left front, rf--> right front etc
        """Einheiten: [mm],[kg]"""
        self.laenge=lenght
        self.breite=width
        self.höhe=hight
        self.linkes_vorderrad=lf
        self.rechtes_vorderrad=rf
        self.linkes_hinterrad=lb
        self.rechtes_hinterrad=rb

auto1=Fahrzeug(4.9, 1.85, 1.5, vorderrad, vorderrad, hinterrad, hinterrad)   #die Vorderräder sind also kleiner als die Hinterräder

# ein Attribut des Rades der Instanz 'auto1' ausgeben 
print(auto1.linkes_hinterrad.durchmesser)
Nun zur eigentlichen Frage: "Darf" man einem Initialisator (__init__) so ohne weiteres nicht "nur" einfach Datentypen (strings, integers, floats etc), sondern auch (wie in meinem Beispiel) ganze Instanzen anderer Klassen übergeben? Könnte mich dieses Verhalten mal in Schwierigkeiten bringen/ wird das genauso in der Praxis gemacht?

Und als abschließende Frage: ist der Zugriff auf den Durchmesser des linken Hinterrades von auto1 so zulässig (wird das so in der Praxis gemacht?)?

Danke schon mal (und sorry, dass es so viel Text geworden ist; wollte es so eindeutig wie möglich formulieren)!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist ganz allgemein vollkommen ok, so vorzugehen. Argumente koennen von jedwedem Typ sein. Und das Prinzip von Komposition, also das ein Objekt eine Reihe von Unterobjekten "managt", ist ein wichtiges und weitverbreitetes Muster.

Was dann wiederum die Frage nach dem Zugriff auf den Hinterraddurchmesser angeht: das macht man tendentiell so nicht. Das ist bekannt als "Demeter's Law". Ein solches durchgreifen auf die Eigenschaften eines Unterobjektes ist ein code-smell, weil es die Abstraktion durchbricht. Um bei deinem natuerlich sehr kuenstlichen Beispiel zu bleiben: was passiert denn, wenn du ploetzlich ein Kettenfahrzeug vor dir hast? Das hat in dem Sinne ja gar keine Raeder, und wenn sogar deutlich mehr als 4, wenn man die verschieden Rollentypen bedenkt.

Die Antwort auf die Frage, was man denn stattdessen macht, ist leider nicht so einfach. Das liegt daran, dass man sich erstmal ein sinnvolles Beispiel ausdenken muss.

Ein Weg, das Beispiel beizubehalten, ginge vielleicht so: du willst aus Gruenden der Wartung von einem Fahrzeug in deinem Fuhrpark wissen, ob es daran Dinge gibt, die durch Kontakt mit der Strasse (oder sogar allgemeiner dem Untergrund) verschlissen sind. Und wissen, ob die konkret ausgewechselt werden muessen.

Dann koennte eine Werkstattfunktion in etwa so aussehen:

Code: Alles auswählen

def warte_fahrzeug(fahrzeug, lager):
    for rad in fahrzeug.raeder:  # umfasst sowohl unsere "normalen" Raeder, als auch Lauf/Treibraeder eines Kettenantriebs
        if rad.verschleiss > 25:
            ersatz = lager.suche_ersatz(rad)
            fahrzeug.ersetze(rad, ersatz)   # wir geben beide rein, damit das Fahrzeug entscheidet, welches Rad genau ersetzt wird
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

Alles klar. Vielen Dank für die schnelle und ausführliche Antwort!
Benutzeravatar
__blackjack__
User
Beiträge: 14078
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wrench139: Was bei dem Beispiel nicht gut ist, ist die Namensgebung. Argumente der `__init__` haben üblicherweise die gleichen Namen wie die Attribute wenn sie 1:1 an solche gebunden werden. Und man sollte sich auf *eine* natürliche Sprache beschränken und nicht mal einen Deutschen und mal an einen Englischen Namen, und das dann teilweise auch noch für den jeweils gleichen Wert verwenden. Kryptische Abkürzungen wie `lf`, `rf`, `lb`, und `rb` gehen auch nicht.

Man nummeriert auch keine Namen. Wenn es sowieso nur eine 1 ist, macht das noch weniger Sinn.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Was bei der aktuellen Variante ganz böse ist: Die Räder links und rechts auf einer Achse sind identisch. Identisch wirklich in dem Sinne, dass es sich nicht um das gleiche Rad handelt, sondern um das selbe Rad. Wenn du irgendwann mal beim Rad noch den Luftdruck hinzufügst, dann merkst du was passiert. Kaum änderst du den Luftdruck im Rad hinten rechts, schon ändert sich auch der Luftdruck hinten links und zwar weil es - wie gesagt - derzeit so modelliert wurde, dass es sich um das selbe Rad handelt.
Benutzeravatar
__blackjack__
User
Beiträge: 14078
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ergänzend: Für Docstrings gilt wie für Kommentare: Kein Docstring ist besser als ein falscher Docstring. Nicht einfach irgendwas kopieren nur um überhaupt einen Docstring zu haben, denn die Kopie von `Rad` passt offensichtlich nicht zu `Fahrzeug`.

Ich verwende für Klassen auch sehr gerne das `attrs`-Package:

Code: Alles auswählen

#!/usr/bin/env python3
from attr import attrib, attrs


@attrs(frozen=True)
class Wheel:
    diameter = attrib()
    mass = attrib()


@attrs(frozen=True)
class Wheels:
    left_front = attrib()
    right_front = attrib()
    left_rear = attrib()
    right_rear = attrib()

    @classmethod
    def from_front_and_rear(cls, front_wheel, rear_wheel):
        return cls(front_wheel, front_wheel, rear_wheel, rear_wheel)


@attrs(frozen=True)
class Vehicle:
    length = attrib()
    width = attrib()
    height = attrib()
    wheels = attrib()


def main():
    front_wheel = Wheel(700, 10)
    rear_wheel = Wheel(750, 14)
    print(front_wheel)
    print(rear_wheel)

    car = Vehicle(
        4.9, 1.85, 1.5, Wheels.from_front_and_rear(front_wheel, rear_wheel)
    )
    print(car)
    print(car.wheels.left_rear.diameter)


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
einfachTobi
User
Beiträge: 512
Registriert: Mittwoch 13. November 2019, 08:38

Half Topic: Die Schreibweise von Einheitenzeichen in eckigen Klammern ist zwar leider gängig, aber falsch. Das ist läuft gegen DIN 461, DIN 1313 und EN ISO 80000-1 ;-)
Antworten