Klassen: Einfache Baumstruktur, Pointer

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
losgehts
User
Beiträge: 19
Registriert: Montag 8. Juni 2009, 01:00

Hallo,

ich bin Neuling in Sachen Python (auch wenn ich mich hier schon vor Jahren angemeldet habe). Sollte ich irgendwie gegen Regeln verstoßen, so ist das ungewollt. Macht mich einfach darauf aufmerksam.

Ich benutze Python 3.4

Ich bin dabei, eine Baumstruktur für mein Projekt zu schreiben. Doch ich habe Schwierigkeiten. Meinen Code habe ich versucht, auf das Problem zu entschlacken:

Code: Alles auswählen

class msgTreeChild:
    __id = ""
    __val = ""
    __parent = None
    __childs = []
    def __init__(self, ID, val):
        self.__val = val
        self.__id = ID

    def addChild(self, ID, Val):
        newChild = msgTreeChild(ID, Val)
        self.__childs.append(newChild)
        return newChild
  
    def getChildList(self):
        return self.__childs


if __name__ == "__main__":
    x = msgTreeChild(1, 2)
    a = x.addChild(2, 3)
    b = x.addChild(3, 1)
    c = x.addChild(4, 2)
    d = x.addChild(5, 2.5)

    # meine Erwartung: x: 4 Kinder,   a: 0 Kinder,   b: 0 Kinder,   c:0,   d:0
    print("Anzahl der Kinder von a: " + str(len(a.getChildList())))
    print(a.getChildList() == b.getChildList())
    print(a.getChildList() is b.getChildList())
Ich bin Neuling, daher bin ich froh, wenn ihr mich verbessert. Ich schreibe kurz, was ich erwartet habe (Schade, wie bekomme ich diese Zeilennummern in die Code-Tags, das wär praktisch):

x = msgTreeChild(1, 2) Es wird eine Instanz der Klasse kreiert und die Methode __init__ wird aufgerufen. Sie weißt der Instanz die Inhalte der Variablen zu.

a = x.addChild(2, 3) Es wird die Methode addChild aufgerufen. Es entspricht quasi:
addChild(self = x, ID = 2, Val = 3).

newChild = msgTreeChild(ID, Val) Es wird eine neue Instanz der Klasse kreiert (damit existieren zwei Instanzen). Die __init__-Methode wird aufgerufen, diese weist die Werte 2,3 den Variablen __id, __val zu.
Der Variablen newChild wird diese neue neue Klasse zugewiesen.

self.__childs.append(newChild) self entspricht x. Es wird also der Liste __childs der ersten Klasse die neue Klasse hinzugefügt.

return newChild Diese neue Klasse wird der Variablen a zugewiesen.

Meinem Verständnis nach müsste ich nun zwei Klassen haben (x und a). x hat in seiner liste __childs einen Eintrag und a hat in seiner Liste __childs kein Element (leere Liste).

Wie ich nun an der Ausgabe der print()-Befehle erkenne, verhält es sich anders, als ich mir das vorstelle.
Kann mir jemand aufzeigen, wo ich den Denkfehler habe? Vielen Dank im Voraus!

Wenn ich a.getChildList() is b.getChildList() == True richtig interpretiere, dann bedeutet das, dass es nur eine Liste "__childs" gibt, und a.__childs sowie b.__childs auf die selbe Instanz verweisen?

Ich bin Euch wirklich dankbar, wenn ihr mir aufzeigt, wo ich das Brett vorm Kopf habe.

Grüße, Ulrich

PS: das mit den Zeilennummern beim Code im Forum habe ich noch rausbekommen: einfach die Code-Tags mit großem "C" beginnen, statt mit kleinem ;-).
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@losgehts: Klassenattribute sind kein Ersatz für Instanzattribute. Diese werden nur einmal, beim Definieren der Klasse, erzeugt; damit hast Du nur eine __childs-Liste, die zwischen allen Instanzen der Klasse geteilt wird.
losgehts
User
Beiträge: 19
Registriert: Montag 8. Juni 2009, 01:00

Hallo Sirius3,

vielen Dank! Oha, das Wort "Instanzattribut" kenne ich bis jetzt noch nicht.

Da muss ich wohl noch einmal ordentlich recherchieren und mich bilden.

Ich verstehe nicht, weshalb die variablen x, a, b, c sich zwar die Liste __childs teilen, jedoch die Werte __val und __id nicht.
Spendiere ich der Klasse noch die Funktionen

Code: Alles auswählen

    def getID(self):
        return self.__id
    
    def getVal(self):
        return self.__val
so ist
a.getVal() == 3
b.getVal() == 1
und x.getVal() == 2
Wo ist da der Unterschied?

Vielen Dank!
Grüße, Ulrich
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@losgehts: Du setzt ja __val und __id in der __init__-Methode. Damit überschreibst Du die Klassenattribute durch Instanzattribute, so dass die Klassenattribute verdeckt sind.
Übrigens solltest Du die beiden Unterstriche vor den Attributen weglassen, dann brauchst Du auch nicht für jedes Attribut eine getter-Methode schreiben, sondern kannst direkt per x.id darauf zugreifen.
BlackJack

Und ergänzend dazu entspricht die Namensgebung auch nicht dem Style Guide for Python Code. Mindestens der Klassenname ist in der Schreibweise sehr ungünstig.

Edit: Die Mehrzahl von „child” ist „children”. Und man sollte Namen nicht ohne Not abkürzen.

Die `addChild()`-Methode macht mehr als man bei dem Namen erwarten würde, nämlich das man ihr einen Kindknoten übergibt und sie den hinzufügt — und nichts zurück gibt.

Code: Alles auswählen

class Tree:

    def __init__(self, id_, value, parent=None):
        self.id = id_
        self.value = value
        self.parent = parent
        self.children = list()
 
    def create_child(self, id_, value):
        result = Tree(id_, value, self)
        self.children.append(result)
        return result
  
 
def main():
    tree = Tree(1, 2)
    a = tree.create_child(2, 3)
    b = tree.create_child(3, 1)
    c = tree.create_child(4, 2)
    d = tree.create_child(5, 2.5)
 
    # meine Erwartung: tree: 4 Kinder, a: 0 Kinder, b: 0 Kinder, c: 0, d: 0
    print('Anzahl der Kinder von a: ', len(a.children))
    print(a.children == b.children)
    print(a.children is b.children)


if __name__ == '__main__':
    main()
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben: Die `addChild()`-Methode macht mehr als man bei dem Namen erwarten würde, nämlich das man ihr einen Kindknoten übergibt und sie den hinzufügt — und nichts zurück gibt.
Und weil die Trennung von Befehl und Abfrage so wichtig ist, gibt es für dieses Konzept sogar einen eigenen Namen: Command Query Separation :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
losgehts
User
Beiträge: 19
Registriert: Montag 8. Juni 2009, 01:00

Hallo,

@Sirius3, vielen Dank für die Ausführung. Ich habe das jetzt ein wenig ausprobiert und verstanden. Muss aber noch einmal richtig in diesem Thema nachlesen (Namensräume, Klassen, wo sind welche Variablen wann sichtbar, wie wird richtig initialisiert...).

@BlackJack, vielen Dank, für das korrigieren! So sieht alles wirklich viel klarer aus. Toll!
Mir fällt das richtig schwer, auf Englisch sprechende Namen zu finden, und den Code so zu strukturieren, dass er leicht zu lesen ist. Und ja, ich hatte zunächst auch "children" im Code, doch irgendwie wurde mir das zu lang. Aber ich werde wieder zurückkehren zu "children".

Also nochmal danke an Euch alle!

Ihr werdet mir sicherlich verzeihen, wenn ich in naher Zukunft trotz der Ratschläge immer mal wieder Kommandos von Abfragen (@Hyperion) nicht klar trenne, oder mich bei der Benennung meiner Funktionen, Klassen und Variablen ungeschickt anstelle ;-) .

Grüße, Ulrich
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

losgehts hat geschrieben:[...] oder mich bei der Benennung meiner Funktionen, Klassen und Variablen ungeschickt anstelle ;-) .
Schau dir auf jeden Fall mal den Style Guide for Python Code an.

Edit: Oh, den hatte BlackJack schon genannt. Man kann ihn aber gar nicht oft genug erwähnen. :wink:
losgehts
User
Beiträge: 19
Registriert: Montag 8. Juni 2009, 01:00

Hallo /me,

jupp habe ich schon in meine Bookmarks aufgenommen.

Danke,
Ulrich
Antworten