Variablen der Oberklasse verändern

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 habe eine Klasse, die von einer Basislasse abgeleitet wurde. In der Basisklasse wurden Variablen definiert. In der abgeleiteten Klasse würde ich gerne eine Funktion der Basisklasse aufrufen, in der die Variablen verändert werden. Leider funktioniert das nicht, so wie vn mir gedacht. Sorry, für die komplizierte Beschreibung. Aber ich denke am Code wird es klar.

Code: Alles auswählen

class A:
    
    def __init__(self):
        self.testvar1 = 0
        self.testvar2 = 0
    
    def incvars(self, var1, var2):
        var1 += 1
        var2 += 1
        print("var1: ", var1,"var2: ", var2)
        
class B(A):

    def myincvars(self):
        A.__init__(self)
        self.testvar1 = 2
        self.testvar2 = 4
        A.incvars(self, self.testvar1, self.testvar2)
        print("Testvar1: ", self.testvar1,"Testvar2: ", self.testvar2)
        
X = B()
X.myincvars()
     
Hier erhalte ich als Ausgabe:
var1: 3 var2: 5
Testvar1: 2 Testvar2: 4
Auf diesem Weg funktionert es natürlich. Aber ich dachte ich bekomme es so wie oben hin.

Code: Alles auswählen

class A:
    
    def __init__(self):
        self.testvar1 = 0
        self.testvar2 = 0
    
    def incvars(self, var1, var2):
        var1 += 1
        var2 += 1
        print("var1: ", var1,"var2: ", var2)
        return var1, var2
        
class B(A):

    def myincvars(self):
        A.__init__(self)
        self.testvar1 = 2
        self.testvar2 = 4
        self.testvar1, self.testvar2 = A.incvars(self, self.testvar1, self.testvar2)
        print("Testvar1: ", self.testvar1,"Testvar2: ", self.testvar2)
        
X = B()
X.myincvars()
var1: 3 var2: 5
Testvar1: 3 Testvar2: 5
Im Internet habe ich ein Beispiel gefunden, wo es funktioniert. Allerdings wird dort ein Array übergeben und keine einzelne Variable.

Code: Alles auswählen

def set( x ):
  x[0] = 3

a = [ 1, 2, 3 ]

print("a vorner:", a)
set( a )
print("a nachher:", a)
a vorner: [1, 2, 3]
a nachher: [3, 2, 3]
Gibt es irgendeine Lösung, dass ich das auf dem ersten Weg hinbekomme?

Danke schon mal für jeden Tip.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@rmanske: Dein vermeintliches Problem hat nichts mit Klassen zu tun. var1 und var2 in incvars sind ganz normale lokale Variablen; ändert man deren Wert, hat das zum Glück keinen Einfluß auf die Variablen, die beim Aufruf übergeben wurden. Nur wenn man den Inhalt (z.B. einer Liste, Arrays sind in Python etwas anderes) ändert, und man außerhalb der Funktion auf diesen Inhalt zugreift, ist der Inhalt, ja nun, verändert.
Daher ist der zweite Weg der Richtige, wobei die Beispielfunktion so sinnfrei ist, dass man daraus nicht wirklich ableiten kann, was Du eigentlich machen möchtest und wie dort die richtige Lösung aussieht. Was ist Dein eigentliches Problem das Du zu lösen versuchst?

Methoden ruft man übrigens mit self.incvars(...) auf und nicht umständlich per A.incvars(self, ...). Supermethoden werden üblicherweise nur von der überschriebenen Methode aufgerufen, vor allem __init__ sollte nie außerhalb von der __init__-Methode der Kindklasse aufgerufen werden.

Methoden erfüllen idealerweise nur eine Funktion, entweder sie liefern einen Wert oder sie ändern den Zustand der Instanz, nicht aber beides. Du hast hier zustandsändernde Methoden:

Code: Alles auswählen

class ClassA:
    def __init__(self):
        self.testvar1 = 0
        self.testvar2 = 0
    
    def incvars(self):
        self.testvar1 += 1
        self.testvar2 += 1
        print("var1: ", self.testvar1,"var2: ", self.testvar2)
        
class ClassB(ClassA):
    def myincvars(self):
        self.testvar1 = 2
        self.testvar2 = 4
        self.incvars()
        print("Testvar1: ", self.testvar1,"Testvar2: ", self.testvar2)
        
b = ClassB()
b.myincvars()
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Danke Dir erstmal für Deine Antwort.
Sirius3 hat geschrieben: wobei die Beispielfunktion so sinnfrei ist, dass man daraus nicht wirklich ableiten kann, was Du eigentlich machen möchtest und wie dort die richtige Lösung aussieht.
Stimmt. Ich wollte niemandem zumuten, um mein eigentliches Problem zu verstehen, die ganze Klasse hier reinzustellen und sich durch den ganzen Code zu quälen. Daher habe ich mir nur ein kurzes Beispiel erstellt, was aber das eigentliche Problem betrifft.
Sirius3 hat geschrieben: Was ist Dein eigentliches Problem das Du zu lösen versuchst?
Das ganze hat zwei Gründe. Zum Einen wollte ich mir den return ersparen, da diese Variablen dann noch für andere Dinge verwendet werden, hatte ich mir gedacht, wenn der Funktionsaufruf direkt, ohne eine Rückgabe, die eigentlichen Variablen ändern könnte, hätte ich mir den Return erspart. Denn in der "realen" Klasse soll der Funktion "incvar" eine ganze Liste übergeben werden, die diese dann direkt bearbeiten soll. Da ich nicht wusste ob folgender Konstrukt funktionieren würde:

Code: Alles auswählen

def Methode_der_Klasse_A(self, Liste)
	mache_irgendwas_mit_Liste
	return Liste

Liste_der_Klasse_B = [[self.testvar1, self.testvar2],  [self.testvar3, self.testvar4]]
Liste_der_Klasse_B = Methode_der_Klasse_A(Liste_der_Klasse_B)

Liste_der_Klasse_C = [[self.testvar5, self.testvar6],  [self.testvar7, self.testvar8]]
Liste_der_Klasse_C = Methode_der_Klasse_A(Liste_der_Klasse_C)
kam mir der Gedanke, die Variablen einzeln zu übergeben und dann aber direkt von der Basisklasse ohne Return ändern zu lassen. Ich habe unten nochmal versucht, dass deutlicher darzustellen, was ich vorhabe. Allerdings ohne Listen. Da der Test mit den Listen in meinem Originalprogramm ziemlich aufwendig zum Testen ist, hatte ich mir überlegt die einzelnen Variablen zu übergeben und dazu ein kurzes Beispiel zusammengestellt.

Zum Anderen will ich noch eine zweite Klasse ableiten. Darin soll wieder dieselbe Funktion aufgerufen werden, aber mit anderen Variablen bzw. Listen. Daher wäre die von Dir vorgeschlagene Lösung mit incvars nicht geeignet.Ich hoffe an dem Beispiel unten wird es deutlicher.

In beiden Klassen werden dieselbe Variablen der Basisklasse benötigt. Aber teilweise eben für unterschiedliche Berechnungen und Zuweisungen.
Sirius3 hat geschrieben: Methoden ruft man übrigens mit self.incvars(...) auf und nicht umständlich per A.incvars(self, ...).
Danke, war mir nicht bekannt.
Sirius3 hat geschrieben:vor allem __init__ sollte nie außerhalb von der __init__-Methode der Kindklasse aufgerufen werden.
Das habe ich nicht verstanden. Wenn Du das hier meinst:

Code: Alles auswählen

    def myincvars(self):
        A.__init__(self)
Das war ein Fehler bei der Erstellung des Kurzbeispiels. Ich rufe die __init__-Methoden nur in den __init_-Methoden der abgeleiteten Klassen auf. Bevor ich hier den Post aufgemacht hatte, habe ich erst einmal selber probiert, ob ich es nicht hinbekomme. Da kam mir der Gedanke, ob es vielleicht an dem fehlenden __init__ liegen könnte und habe einfach mal probiert. Sorry, war natürlich hier im Beispiel blöd, da ich es mir auf die schnelle zusammengeschrieben hatte. Hätte ich vor dem posten wieder rausnehmen sollen.
Sirius3 hat geschrieben: Supermethoden werden üblicherweise nur von der überschriebenen Methode aufgerufen,
Das habe ich nicht verstanden. Wenn Du das wie im obigen Beispiel meinst, dann stimme ich Dir zu. Mache ich auch nicht anders in der "realen" Klasse. Dort wird nur die eigentliche Methode überschrieben und die Methode der Basisklasse auch von anderen Methoden der abgeleiteten Klasse nicht aufgerufen. Wie bereits geschrieben, war es nur ein Kurzbeispiel, das aus meinen Tests übriggeblieben ist.

Code: Alles auswählen

class A:
   
    def __init__(self):
        self.testvar1 = 0
        self.testvar2 = 0
        self.testvar3 = 0
        self.testvar4 = 0
   
    def incvars(self, var1, var2):
        var1 += 1
        var2 += 1
        
class B(A):

    def myincvars(self):
        self.testvar1 = 2
        self.testvar2 = 4
        self.incvars(self.testvar1, self.testvar2)

class C(A):

    def myincvars(self):
        self.testvar3 = 6
        self.testvar4 = 8
        self.incvars(self.testvar3, self.testvar4)

X = A()
X.myincvars()
       
Y = B()
Y.myincvars()
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Ich habe gerade nochmal etwas getestet. Folgendes Beispiel macht genau das was ich erreichen will:

Code: Alles auswählen

class A:
    
    def __init__(self):
        self.testvar1 = 0
        self.testvar2 = 0
    
    def incvars(self, listvar):
        #for i, var1, var2 i in range(len(listvar)):
        for i, var1 in enumerate(listvar):
            var1[0] += 1
            var1[1] += 3
        
class B(A):

    def myincvars(self):
        self.testvar1 = 2
        self.testvar2 = 4
        self.testvar3 = 12
        self.testvar4 = 14
        Liste = [[self.testvar1,self.testvar2], [self.testvar3,self.testvar4]]
        print(Liste)
        self.incvars(Liste)
        print(Liste)

X = B()
X.myincvars()
[[2, 4], [12, 14]]
[[3, 7], [13, 17]]
Aber ohne einen return. Das verwirrt mich jetzt.
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Ich habe es die ganze Zeit versucht, aber komme mit meinem Ansatz nicht weiter. Ich versuche das mal konkreter zu beschreiben. Das Problem hängt mit der Matplotlib zusammen. Ich hatte ursprünglich folgendes:

Code: Alles auswählen

class PlotBaseClass(object):
    '''
    classdocs
    '''
    colourcount = 0   
    
    def __init__(self, fig, canv):
        '''
        Constructor
        '''
        self.figure = fig
        self.canvas = canv
        # Variablen für die Y-Werte der Graphen
        self.Graph_1 = []
        self.Graph_2 = []
        self.Graph_3 = []
        self.Graph_4 = []
        
        # Variablen für die matplotlib-Graphen
        self.Graph1  = []
        self.Graph2  = []
        self.Graph3  = []
        self.Graph4  = []
        
        self.Graph1Label    = 'Graph1'
        self.Graph2Label    = 'Graph2'
        self.Graph3Label    = 'Graph3'
        self.Graph4Label    = 'Graph4'
        
           
    def PlotGraphs(self):
        self.ax = self.figure.add_subplot(111)
        
        marker1 = 'o'
        marker2  = 's'
        if not len(self.Graph_1) == 0:
            self.Graph1, = self.ax.plot(self.Graph_1,
                                        marker=marker1 , 
                                        label=self.Graph1Label, 
                                        lw=self.LineWidth, 
                                        color=colors_[type(self).colourcount])
    
        if not len(self.Graph_2) == 0:
            self.Graph2, = self.ax.plot(self.Graph_2,
                                        marker=marker1 , 
                                        label=self.Graph2Label, 
                                        lw=self.LineWidth,
                                        color=colors_[type(self).colourcount])

            type(self).colourcount = inccolocount(type(self).colourcount)
   
        self.ax2 = self.ax.twinx()
        if not len(self.Graph_3) == 0:
            self.Graph3, = self.ax2.plot(self.Graph_3, 
                                        marker=marker2  ,
                                        label=self.Graph3Label, 
                                        lw=self.LineWidth,
                                        color=colors_[type(self).colourcount])
            type(self).colourcount = inccolocount(type(self).colourcount)
    
        if not len(self.Graph_4) == 0:
            self.Graph4, = self.ax2.plot(self.Graph_4, 
                                        marker=marker2  , 
                                        label=self.Graph4Label, 
                                        lw=self.LineWidth,
                                        color=colors_[type(self).colourcount])
        self.ax.grid()
        self.canvas.draw()
       
  
    def ChangeLineWidth(self, value):
        self.LineWidth = value
        self.Graph1.set_linewidth(value)
        self.Graph2.set_linewidth(value)
        self.Graph3.set_linewidth(value)
        self.Graph4.set_linewidth(value)
Jetzt soll die Basisklasse sich aber nicht darum kümmern, ob Werte vorhanden sind. Es kann auch mal sein, dass einer der Graphen nicht gezeichnet werden soll. Darum soll sich die abgeleitete Klasse kümmern.

Mein neuer Ansatz war folgender, die Basisklasse abzuändern, das darin die Funktion PlotGraphs geändert wird, wie unten, diese dann in der abgeleiteten Klasse aufgerufen wird.

Code: Alles auswählen

   def PlotLeftGraph(self, PlotList):
        self.ax = self.figure.add_subplot(111)
        for Item in PlotList:
            Item[0] = self.ax.plot(Item[1], marker=Item[2],
                                   label= Item[3],
                                   lw= Item[4],
                                   color=colors_[type(self).colourcount])
       return PlotList                
In der abgeleiteten Klasse steht dann das:

Code: Alles auswählen

       
              self.LeftGraph = [ self.Graph1, self.Graph_2, 
                            self.marker1, self.Graph1Label, 
                            self.LineWidth],
                           [self.Graph2, self.Graph_2, 
                            self.marker1, self.Graph2Label, 
                            self.LineWidth]]
       self.LeftGraph = self.PlotLeftGraph(self.LeftGraph)
Dies funktioniert aber nicht. Ich muss mir die Rückgabe von self.ax.plot ( in self.Graph1, self.Graph2, self.Graph3, self.Graph4) irgendwie speichern, weil ich sie später nochmal z.B. beim ändern der Linienbreite der einzelnen Graphen benötige. Aber die Sache mit Item[0] = self.ax.plot funktioniert nicht.

Daher war mein Ansatz, wenn die Basisklasse dies direkt in die entsprechenden Variablen schreibt, dann bräuchte ich mich darum nicht zu kümmern. Ich weiss nicht, wie ich das lösen soll.

Bin für jeden Ratschlag dankbar.
Zuletzt geändert von Anonymous am Donnerstag 6. Oktober 2016, 11:47, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@rmanske: statt Variablennamen durchzunummerieren nimmt man am besten Listen und wenn man einen Kommentar braucht, um den Variablennamen zu erklären, dann ist der Variablennamen garantiert schlecht. Wie soll man am Namen erkennen, was der Unterschied zwischen Graph_1 und Graph1 ist? Alle Attribute sollten schon in __init__ angelegt werden, notfalls mit None als Wert (self.ax = None).
Klassenvariablen sind schlecht, weil dann abhängig von der Art, wie ich irgendwo irgendwelche Methoden aufrufe Graphen mit unterschiedlichen Farben erzeugt werden.

Die einzelnen Graphen sind zu komplex, um sie einfach innerhalb deiner Plot-Klasse zu verwalten. Mach daraus eine eigene Klasse.

Code: Alles auswählen

COLORS = ['red', 'green', 'blue', 'yellow']

class Graph(object):
    MARKERS = {
        'left': 'o',
        'right': 's',
    }
    def __init__(self, values, label, xaxis='left', linewidth=1):
        self.values = values
        self.label = label
        self.xaxis = xaxis
        self.graph = None
        self.linewidth = linewidth
        
    def plot(self, axes, color):
        if not self.values:
            return
        self.graph = axes[self.xaxis].plot(self.values,
            marker=self.MARKERS[self.xaxis],
            label=self.label,
            lw=self.linewidth,
            color=color)[0]

    def change_linewidth(self, linewidth):
        self.linewidth = linewidth
        if self.graph:
            self.graph.set_linewidth(value)
            
class PlotBaseClass(object):
    def __init__(self, fig, canv, graphs):
        self.figure = fig
        self.canvas = canv
        self.axes = None
        self.graphs = graphs
           
    def plot(self):
        self.axes = {}
        self.axes['left'] = self.figure.add_subplot()
        self.axes['right'] = self.axes['left'].twinx()
        for graph, color in zip(graphs, COLORS):
            graph.plot(self.axes, color)
        self.canvas.draw()
 
    def change_linewidth(self, linewidth):
        for graph in graphs:
            graph.change_linewidth(linewidth)

            
graphs = [
    Graph(values_a, "Graph1", xaxis="left"),
    Graph(values_b, "Graph2", xaxis="left"),
    Graph(values_c, "Graph3", xaxis="right"),
    Graph(values_d, "Graph4", xaxis="right"),
]
plots = PlotBaseClass(fig, canv, graphs)            
Alternativ könnte man auch statt der graphs-Liste mit einem (sortierten) Wörterbuch arbeiten, dann kann man auf die einzelnen Graphen auch per Label zugreifen.
rmanske
User
Beiträge: 26
Registriert: Freitag 30. September 2016, 13:26

Danke

Die Idee ist klasse. Ich breche mir hier die ganze Zeit einen ab. Wäre ich nie drauf gekommen, bin schon etwas betriebsblind. :mrgreen:
Antworten