Instanzvariablen in Ober- und Unterklasse

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
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Servus Forum,

ich habe eine Frage zu der ich selbst keine Lösung weiß, bzw. finde.
Folgendes Problem:
Es gibt zwei Klassen, von der eine Klasse von der anderen erbt.
In der Oberklasse wird aus einer CSV-Datei, Datensätze in eine Liste eingetragen.
Wie bekomme ich nun die Daten die sich in der Liste befinden in die Unterklasse,
bzw. wie bekomme ich die Liste samt ihrer Daten in die Oberklasse zurück wenn sie von der Unterklasse verändert wurden?

Code: Alles auswählen

import csv

class Personenliste():
    def __init__(self):
        self.tabelle = []
        # 1, "Max", "Mustermann", "True"
        # 2, "Bärbel", "Mustermann", "False"

        self.feldnamen = []
        #Nummer, Vorname, Nachname, "Mitglied"

    def tabelle_einlesen(self, csvdatei):
        with open(csvdatei, "r", encoding="utf-8") as datei:
            reader =  csv.DictReader(datei, delimiter=";")
            self.feldnamen = reader.fieldnames

            for zeile in reader:
                self.tabelle.append(zeile)
                

class Mitgliedschaft(Personenliste):
    def __init__(self):
        super(Liste, self).__init__()
        # ???

    def mitgliedschaft_aufheben(self):
        for person in self.tabelle:
            if person["Mitglied"] == "True":
                person["Mitglied"] = "False"


PL = Personenliste()
PL.tabelle_einlesen("mitglieder.csv")

Mitgliederliste = Mitgliedschaft()
Mitgliederliste.mitgliedschaft_aufheben()

Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Begriffe Ober- und Unterklasse verwendet man nicht. Es heißt Eltern- und Kindklasse.
Und Vererbung ist etwas abstraktes. Alle Eigenschaften der Eltern werden auf das Kind übertragen, aber wenn man einem Elternteil etwas sagt, weiß das das Kind noch lange nicht.

Die Klasse `Mitgliedschaft` ist falsch benannt, denn Mitgliedschaft ist eine Eigenschaft, besser wäre Personenliste_mit_Mitgliedschaft.

Code: Alles auswählen

import csv

class Personenliste():
    def __init__(self):
        self.tabelle = []
        self.feldnamen = []

    def tabelle_einlesen(self, csvdatei):
        with open(csvdatei, encoding="utf-8") as datei:
            reader =  csv.DictReader(datei, delimiter=";")
            self.feldnamen = reader.fieldnames
            self.tabelle.extend(reader)
                

class Personenliste_mit_Mitgliedschaft(Personenliste):
    def mitgliedschaft_aufheben(self):
        for person in self.tabelle:
            if person["Mitglied"] == "True":
                person["Mitglied"] = "False"


mitgliederliste = Personenliste_mit_Mitgliedschaft()
mitgliederliste.tabelle_einlesen("mitglieder.csv")
mitgliederliste.mitgliedschaft_aufheben()
Nur Klasssen schreibt man Großklein, Variablen werden komplett klein geschrieben.
Zuletzt geändert von Sirius3 am Dienstag 9. Juni 2020, 23:01, insgesamt 1-mal geändert.
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hartmannsgruber: Die Frage macht keinen Sinn. Warum erstellst Du eine Personenliste? Erstelle eine `Mitgliedschaft` (das sollte wohl eher `Mitgliederliste` heissen, wo dann auch klar wird warum man Namen die keine Klassen bezeichnen klein_mit_unterstrichen schreibt). Eine `Mitgliedschaft` *ist* eine `Personenliste`. Da müssen keine Daten ausgetauscht werden, die sind dann ja schon *da*, auf dem Objekt selbst.

Eine `__init__()` die nichts anderes macht als die `__init__()` der Basisklasse aufzurufen kann man auch einfach weg lassen.

Bei CSV-Dateien ist das ``newline=""``-Argument beim öffnen der Datei wichtig.

`super()` sollte man keine Argumente übergeben. Dann kann es auch nicht passieren, dass man falsche übergibt, wie das nicht existierende `Liste` im Beispiel.

`csvdatei` ist kein guter Name für einen Datei*namen*. Bei `*datei` erwartet der Leser, dass es sich um ein Dateiobjekt handelt, also Methoden wie `read()`, `write()`, `close()` auf dem Objekt gibt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Guten Morgen,

vielen Dank an die schnellen Antworten!
Die Fehler die sich eingeschlichen haben, nehme ich mir zu Herzen und werde diese auch nicht mehr machen.

Das Beispiel das ich gewählt habe war nicht das Beste, mir ist aber gestern Abend nichts besseres eingefallen.
Ob das in dem Codeschnippsel nun sinnvoll ist oder nicht sei dahingestellt, aber wie funktioniert so etwas?

Wie kann eine Instanzvariable mit Daten der Elternklasse an eine Kindklasse übergeben, von dieser verändert und an die
Elternklasse zurück gegeben werden?
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

@Hartmannsgruber: da muß nichts übergeben werden. Elternklasse und Kindklasse sind Definitionen, wobei die Kindklasse alles, was in der Elternklasse definiert wurde, einfach übernimmt. Klassendefinitionen haben keine Instanzvariablen. Auch wenn die Definition in mehrere Klassen aufgeteilt ist, das ist für die Instanz egal, jede Methode, egal ob in der Eltern- oder der Kindklasse definiert, greift über `self` auf die SELBE Instanz zu.
Wie es geht, habe ich doch im Code gezeigt. Wo verstehst Du konkret etwas daran nicht?
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

@Sirius3
Die Begriffe Ober- und Unterklasse verwendet man nicht. Es heißt Eltern- und Kindklasse.
Ist verstanden.
Und Vererbung ist etwas abstraktes. Alle Eigenschaften der Eltern werden auf das Kind übertragen, aber wenn man einem Elternteil etwas sagt, weiß das das Kind noch lange nicht.
Dies ist mir auch klar. Es ist ja nur etwas wie eine Art "Bauplan" das das Kind erhält.
Daher war auch meine Frage, wie ich Daten die später in der Elternklasse entstehen an das Kind gesendet werden können.

Ich habe nochmal ein kurzes Beispiel geschrieben:

Code: Alles auswählen

class Eltern():
    def __init__(self):
        self.var1 = "Eltern"
        self.liste = []

    def liste_fuellen(self):
        for a in range(0,10):
            self.liste.append(a)
        

class Kind(Eltern):
    def __init__(self):
        super().__init__()
        self.var2 = "Kind"

    def liste_umdrehen(self):
        self.liste = self.liste[::-1]
        
        

e = Eltern()
print(e.var1)			# -> Eltern
print(e.liste)			# -> []
e.liste_fuellen()
print(e.liste)			# -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print("################")

k = Kind()
print("Variable aus Kindklasse:", k.var2)	# -> Variable aus Kindklasse: Kind
print("Variable aus Elternklasse:", k.var1)	# -> Variable aus Elternklasse: Eltern
print(k.liste)					# -> []
Wie wäre nun ein möglicher Lösungsweg die Werte die sich nach dem aufruf von self.liste_fuellen in self.liste der Elternklasse befinden
an eine Instanz der Kindklasse zu übertragen? Es ist mir klar, dass ich self.liste_fuellen in der Kindklasse aufrufen kann, dies ist aber gerade nicht der Sinn.

Bzw. die Kindklasse besitzt die Funktion self.liste_umdrehen, die die Elternklasse nicht besitzt.
Wie kann nun die "umgedrehte" Liste (wieder) an eine Instanz der Elternklasse gesendet werden?

Ist meine Frage so gestellt verständlich, oder denke ich einfach nur zu kompliziert?
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Du denkst einfach nur zu kompliziert, oder ich verstehe Dein Problem nicht. Warum hast Du zwei Instanzen, e und k? (und nein, auch bei simplen Beispielen sind gute Variablennamen wichtig, weil sie zum Verständnis des Codes essentiell sind, vor allem, ob der Fragesteller den Code selbst verstanden hat).

Kannst Du mal ein wirkliches Beispiel geben, von dem Problem, das Du versuchst zu lösen?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hartmannsgruber: Die Frage ist zumindest für mich unverständlich beziehungsweise sehe ich nicht was die mit Vererbung zu tun hat. Denn die Antwort ist die gleiche selbst wenn die *Typen* von `e` und `k` keine Vererbungsbeziehung hätten: das sind zwei völlig voneinander unabhängige Objekte. Wenn Du da Daten zwischen denen übertragen möchtest, dann musst Du das *tun*. Also beispielsweise nach dem Umdrehen der Liste von `k` ein ``e.liste = k.liste``.

Die Frage ist dann eher warum Du überhaupt ein Exemplar von `Eltern` erstellst. Denn ein `Kind` ist ja gleichzeitig auch ein `Eltern` und kann und besitzt alles was ein `Eltern`-Objekt hätte, kann also auch überall verwendet werden wo eigentlich ein `Eltern`-Objekt erwartet wird. Also zumindest wenn man das so programmiert, was man machen sollte. Stichwort Liskovsches Substitutionsprinzip.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Nach den letzten beiden Antworten ist mir der ganze Ablauf klarer geworden.
Ich habe nur zu kompliziert gedacht, die Ausführung erscheint für mich nun nachvollziehbar und logisch.
Ich habe die letzten Tage selbst an ein paar Beispielen zu Hause herumgeübt.

Eine kleine Frage stellt sich mir nun leider doch noch:

Code: Alles auswählen

class KlasseA():
    def __init__(self):
        self.aliste = [1, 2, 3, 4, 5]

class KlasseB():
    def __init__(self):
        self.bliste = []

    def bliste_fuellen(self, other):
        self.bliste = other

    def bliste_umdrehen(self):
        self.bliste = self.bliste[::-1]

    def bliste_zurueckschreiben(self, other):
        other = self.bliste


ka = KlasseA()
print("ka.aliste ->", ka.aliste)
print("#\n")

kb = KlasseB()
print("kb.bliste ->", kb.bliste)
print("##\n")

kb.bliste_fuellen(ka.aliste)
print("kb.bliste nach füllen ->", kb.bliste)
print("###\n")

kb.bliste_umdrehen()
print("kb.bliste nach umdrehen ->", kb.bliste)
print("####\n")

kb.bliste_zurueckschreiben(ka.aliste)
print("nach ka.aliste zurückgeschrieben ->", ka.aliste)
Im Beispiel handelt es sich nun um zwei unabhängige Klassen wie es __blackjack__ geschrieben hat.
Hier werden nun von einer Klasse die Daten in die andere übernommen.

1. Ist die Bezeichnung mit "other" korrekt, oder wird hier standardmäßig ein anderer verwendet?
2. Wann werden setter und getter verwendet, bzw. werden solche Funktionen bereits bei solchen Situationen verwendet?
3. Die letzte Funktion "bliste_zurueckschreiben schreibt keine neue liste in die Instanzvariable aliste, warum?
Wäre wieder sehr dankbar um Hilfe.
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hartmannsgruber: 1. `other` ist hier eher sehr verwirrend. Das wird in der Regel für ”gleichwertige”/”gleichrangige” Objekte, oft vom selben Typ verwendet, wie das auf dem die Methode definiert ist. Also beispielsweise wenn man eine `Point`-Klasse hat die einen kartesischen Punkt darstellt und dem eine `distance()`-Methode gibt kann sieht man oft die Signatur `distance(self, other)` wobei `other` dann der andere Punkt ist, oder zumindest ein anderes geometrisches Objekt.

2. Triviale Getter/Setter werden in Python gar nicht verwendet. Womit `bliste_fuellen(), was nebenbei bemerkt nichts füllt, und eigentlich `set_bliste()` heissen müsste, schon mal weg kann. Als nächtes ist dann die Frage warum es Aufgabe von `KlasseB` ist ein Attribut auf Objekten vom Typ `KlasseA` zu setzen. Bei einem Spielzeugbeispiel mit total nichtssagenden Namen wird es schwer sein, dafür eine Begründung zu finden.

3. Das ist vereinfacht das gleiche wie:

Code: Alles auswählen

a = [1, 2, 3]
b = a
b = [4, 5, 6]
print(a)  # → [1, 2, 3] und nicht [4, 5, 6]
Das Du dem Namen `other` einen neuen Wert zuweist hat genau Null Auswirkungen auf den Wert der dem Namen vorher zugewiesen war.

Das ganze Beispiel schreit IMHO förmlich nach Folgefehlern weil Python von sich aus nie Werte kopiert bei Zuweisungen oder Argumentübergaben. `bliste_fuellen()` füllt keine Liste sondern ersetzt das Attribut durch das was übergeben wird. Beim Code der das benutzt, bedeutet das, dass `ka` und `kb` nach dem Aufruf die *selbe* Liste verwenden. Man müsste da dann theoretisch die Liste gar nicht mehr ”zurück schreiben” wenn `bliste_umdrehen()` tatsächlich das machen würde was der Name suggeriert. Macht es aber nicht — es wird dort eine neue Liste erstellt und nicht die vorhandene umgedreht. Ob das in der Praxis dann ein Fehler ist, hängt davon ab, was `KlasseB` und `bliste` tatsächlich ist, und ob Objekte der `KlasseB` davon der ”Besitzer” sind, der das einfach austauschen darf, oder die Erwartung besteht, dass sie das Objekt von irgendwoher ”geliehen” bekommen haben und es zum manipulieren bekommen haben.

Der Beispielcode würde also eher so aussehen:

Code: Alles auswählen

class A:
    def __init__(self):
        self.values = [1, 2, 3, 4, 5]


class B:
    def __init__(self):
        self.values = []

    def values_umdrehen(self):
        self.values = self.values[::-1]


a = A()
print("a.values ->", a.values)
print("#\n")

b = B()
print("b.values ->", b.values)
print("##\n")

b.values = a.values
print("b.values nach setzen ->", b.values)
print("###\n")

b.values_umdrehen()
print("b.values nach umdrehen ->", b.values)
print("####\n")

b.values = a.values
print("a.values nach setzen ->", a.values)
“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

Hartmannsgruber hat geschrieben: Dienstag 16. Juni 2020, 08:41 Hier werden nun von einer Klasse die Daten in die andere übernommen.
Ich habe noch ein Problem mit dem Begriff "übernommen". Vielleicht ist es nur unglücklich ausgedrückt, vielleicht ist aber auch das Verständnis noch falsch. Es werden keine Daten aus einer Klasse in die andere übernommen. Wenn etwas übernommen wird, dann ist es der Bauplan der Klasse.

Die beiden folgenden Beispiele sind für deine Zwecke gleichwertig.

Code: Alles auswählen

    class Foo:
        def __init__(self):
            self.number = 23

    class Bar(Foo):
        def __init__(self):
            super().__init__()
            self.text = f'Hello World {self.number}!'

    baz = Bar()
    print(baz.text)

Code: Alles auswählen

    class Bar:
        def __init__(self):
            self.number = 23
            self.text = f'Hello World {self.number}!'

    baz = Bar()
    print(baz.text)
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@/me: Bei `Foo` fehlt der `super()`-Aufruf der `object.__init__()`. Und man sollte auch immer beliebige Argumente erwarten und weitergeben. `super()` ist halt Superscheisse. Oder zumindest ”unbenutzbar”. 😈

Code: Alles auswählen

class Foo:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.number = 23

class Bar(Foo):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.text = f'Hello World {self.number}!'

baz = Bar()
print(baz.text)
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten