Probleme mit interner Klassenvariable

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
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Hallo,

ich heisse Rainer und versuche mich gerade in Python einzuarbeiten.
Ich habe ein Problem mit einer Klasse, die ich in eine Liste einfügen will. Dies funktioniert aber leider nicht. Die Instanz wird erzeugt und in die Liste eingefügt, aber die internen Variablen der Klasse werden immer gleich beschrieben. Ich bin davon ausgegangen, wenn ich die Instanz erzeuge, dass dann auch ein neuer Speicherbereich reserviert wird (so wie in C++ mit new). Aber es wird immer die gleiche interne Variable beschrieben.

Da das Ganze ziemlich verwirrend ist, habe ich mal den Code und die Ausgabe unten reingestellt.

Code: Alles auswählen

klassenliste = []

class myclass():
    __myvar = []
    
    def __init__(self, FileName):
        self.__csvFileName          = FileName
        #self.__myvar = []
        self.__myvar.append(FileName)
        
    def getFilename(self):
        return self.__csvFileName, self.__myvar

def testfunc():    
    
    
    for i in range(10):
        x = myclass("Test"+str(i))
        klassenliste.append(x)
    
    for i in range(10):
        print(klassenliste[i].getFilename())

if __name__ == '__main__':
    testfunc()
Hierbei erhalte ich als Ausgabe:
('Test0', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test1', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test2', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test3', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test4', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test5', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test6', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test7', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test8', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
('Test9', ['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'])
Wenn ich dagegen folgendes mache:

Code: Alles auswählen

class myclass():
    #__myvar = []
    
    def __init__(self, FileName):
        self.__csvFileName          = FileName
        self.__myvar = []
        self.__myvar.append(FileName)
        
    def getFilename(self):
        return self.__csvFileName, self.__myvar

def testfunc():    
    
    
    for i in range(10):
        x = myclass("Test"+str(i))
        klassenliste.append(x)
    
    for i in range(10):
        print(klassenliste[i].getFilename())

if __name__ == '__main__':
    testfunc()
ist die Ausgabe so wie ich es erwartet hatte:
'Test0', ['Test0'])
('Test1', ['Test1'])
('Test2', ['Test2'])
('Test3', ['Test3'])
('Test4', ['Test4'])
('Test5', ['Test5'])
('Test6', ['Test6'])
('Test7', ['Test7'])
('Test8', ['Test8'])
('Test9', ['Test9'])
Ich muss die Variable im Init angeben, dann funktioniert alles richtig. Ist das nicht so, wenn ich die Variable im Kopf der Klasse angebe, dass hier auch ein neuer Speicherbereich reserviert wird? Dies kenn ich von C++ so, daher hatte ich das bei Python auch angenommen. Oder mache ich etwas anderes falsch?

Danke im Voraus für eine Antwort.

Rainer
Zuletzt geändert von Anonymous am Freitag 30. September 2016, 15:59, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@rmanske: genau das ist der Unterschied zwischen Klassen- und Instanzattributen. Während Klassenattribute im Klassenrumpf definiert werden und damit für alle Instanzen der Klasse gleich sind, werden Instanzattribute in __init__ angelegt.

Zur Namenskonvention: Klassen werden üblicherweise mit GrossBuchstaben am Anfang geschrieben, während Variablen und Attribute klein_mit_unterstrich geschrieben werden. Zwei führende Unterstriche bei Attributnamen sind selten sinnvoll, nimm maximal einen. getFilename tut nicht das, was man von der Methode erwartet. Auch ist es unüblich triviale Getter zu schreiben. Man kann auch direkt auf die Attribute (dann ohne Unterstrich) zugreifen: x.csv_filename
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Hallo,

danke für Deine Antwort. Ich werde es mir merken.

Was den Rest angeht (Zugriff ohne Getter und getFilename) habe ich das jetzt nur auf die schnelle zusammenkopier. An getFilename nur noch schnell die Ausgabe dran gehangen. Hätte ich meine gesamte Klasse, wo das eigentliche Problem ist, hier reingestellt wäre das unheimlich lang geworden.

Daher nur zusammenkopiert. In der richtigen Klasse macht getFilenename das was es soll. :D

Hatte mir auch überlegt direkt zuzugreifen. Werde ich wohl noch ändern.

Aber auf jeden Fall Danke für den Hinweis mit Klassen- und Instanzenattibuten. Werde mir da noch was zu ergoogeln.

Schönes Wochenende.
BlackJack

@rmanske: Stell Dir Klassenvariablen als ``static`` vor, wenn Du mit C++ vergleichen möchtest.

Als nächstes versuch das public, protected, private Denken los zu werden. Das gibt es in Python nicht. Die doppelten führenden Unterstriche verwendest Du falsch. Die sind nicht für den Zugriffsschutz, die schützen davor nämlich nicht wirklich, sondern um bei Mehrfachvererbung oder tiefen Vererbungshierarchien Namenskollisionen zu vermeiden. Beides hast Du hier nicht. Und hat man in Python allgemein nicht, oder nur *sehr* selten.

Attribute die nicht zur öffentlichen API gehören, kennzeichnet man mit *einem* führenden Unterstrich.

`klassenliste` ist ein falscher Name denn Du steckst da gar keine Klassen rein. Das könnte man in Python tatsächlich tun, denn Klassen sind auch Objekte. Wie alles was man an einen Namen binden kann. Also auch Funktionen oder Module beispielsweise. Die Liste sollte zudem nicht global auf Modulebene stehen.

Die zweite ``for``-Schleife ist ”unpythonisch”. Laufindexvariablen braucht man in Python ziemlich selten. Man kann direkt über die Elemente der Liste iterieren.

`str()` und ``+`` um Werte und Zeichenketten zu verbinden ist eher BASIC als Python. Python kennt eine `format()`-Methode auf Zeichenketten.

Code: Alles auswählen

class Class(object):
   
    def __init__(self, filename):
        self._csv_filename = filename
        self._filenames = list()
        self._filenames.append(filename)
       
    def get_values(self):
        return self._csv_filename, self._filenames


def main():    
    instances = [Class('Test{0}'.format(i)) for i in range(10)]
    for instance in instances:
        print(instance.get_values())

 
if __name__ == '__main__':
    main()
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Danke Dir für Deine ausführlichen Erklärungen.

Bin als Neuling froh über jede Erklärung.

Wie bereits geschrieben, war es schnell rauskopiert um das Problem zu schildern. Die eigentliche Klasse, in der das Problem aufgetreten ist, wäre hier zu lang gewesen. Wollte keinem zumuten sich durch den Code zu quälen. Daher habe ich mir ein Beispiel gestrickt wo das Problem auch auftaucht.

Mit den Unterstrichen werde ich ändern.
Antworten