Klassen | Python-Kurs | Methoden |
Eine Klasse besteht aus Variablen, die in diesem Zusammenhang Attribute genannt werden, und Funktionen, die man hier Methoden nennt.
In dem Abschnitt über Klassen haben wir die Klasse Kiste kennengelernt, die die Attribute breite, hoehe und tiefe hat.
Private und Public Member
In der Software-Entwicklung gilt das Geheimnisprinzip. Alles, was nicht unbedingt öffentlich sein muss, sollte vor anderen Teilen des Programms verborgen sein.Bei Python hat die Philosophie, dass Programmierer erwachsen sind und nicht vor sich selbst beschützt werden müssen. Das mag man zwar bei der Betrachtung des einen oder anderen Kollegen in Zweifel ziehen, führt aber oft zu sehr schlanken und effizienten Lösungen.
Die Konvention besagt, dass die Privatheit von Attributen durch vorangestellte Unterstriche notiert wird und sich doch bitte alle daran halten sollen. Beginnt der Bezeichner ...
- ... ohne Unterstrich: Das Attribut (oder die Methode) ist öffentlich,
also public. Sie kann jederzeit als offizielle Schnittstelle zugegriffen
werden.
class Kiste: def __init__(): self.breite = 0 ... meinekiste = Kiste() meinekiste.breite = 1
- ... mit einem Unterstrich: Das Attribut (oder die Methode) ist ist für
den internen Gebrauch gedacht. Nur Rüpel werden diese von außen zugreifen.
Der Zugriff ist aber für diese technisch noch möglich.
Lediglich beim import werden diese Bezeichner nicht weitergereicht.
class Kiste: def __init__(): self._breite = 0 ... meinekiste = Kiste() meinekiste._breite = 1 # so etwas tut man nicht!
- ... mit zwei Unterstrichen: Die Variable ist privat. Sie soll nur über
den Umweg von Methoden zugegriffen werden.
Ein direkter Zugriff ist nicht so direkt möglich. Allerdings, so ganz
unmöglich ist es nicht. Wenn man zuvor einen Unterstrich und den
Klassennamen anführt, kommt man immer noch an die Variable.
Aber dann sollte man wirklich wissen, was man tut.
class PrivateKiste: def __init__(self, b, h, t): self.__breite = b ... pk = PrivateKiste(3,5,7) print(pk.__breite) # das knallt! Variable nicht bekannt. print(pk._PrivateKiste__breite) # das funktioniert. Sollte man aber nicht tun.
- Ein Unterstrich am Ende eines Bezeichners wird verwendet, um Kollisionen mit Schlüsselwörtern von Python zu vermeiden. Soll eine Variable class oder def heißen, wird das den Interpreter unglücklich machen. Aber class_ und def_ vermeidet das Problem.
- Zwei Unterstriche am Anfang und am Ende eines Bezeichners nennt Python magische Elemente. Beispielsweise __init__. Als Programmierer dürfen Sie gern die Magie von Python benutzen, aber nicht eine eigene erzeugen.
Privatisierung
Definieren wir die Kiste mit privaten Attributen, ergibt sich folgendes Bild:class Kiste: def __init__(self, b, h, t): self.__breite = b self.__hoehe = h self.__tiefe = t def getVolumen(self): self.__vol = self.__breite*self.__hoehe*self.__tiefe return self.__vol pk = Kiste(3, 5, 7)Diese Konstruktion führt dazu, dass die Dimension der Kiste nur bei der Erzeugung des Objekts festgelegt werden kann. Anschließend ist sie nicht mehr änderbar. So etwas kann durchaus gewollt sein.
Sollen die Dimensionen auch nachträglich geändert werden können, muss eine entsprechende Methode eingebaut werden. Nach Konvention verwendet man dazu eine Methode, die nach dem Attribut benannt ist, allerdings mit einem set davor, beispielsweise setBreite für das Attribut breite.
class Kiste: def __init__(self, b, h, t): self.__breite = b self.__hoehe = h self.__tiefe = t def setBreite(self, breite): self.__breite = breite def setHoehe(self, hoehe): self.__hoehe = hoehe def setTiefe(self, tiefe): self.__tiefe = tiefe def getVolumen(self): self.__vol = self.__breite*self.__hoehe*self.__tiefe return self.__volNun ergibt sich die Möglichkeit, die Berechnung des Volumens von getVolumen in die set-Funktionen auszulagern. getVolumen würde dann einfach nur die Varialbe _vol zurückgeben. So würde das Volumen nur dann neu berechnet, wenn wirklich ein Attribut geändert wird.
class Kiste: def __init__(self): self.__breite = 0 self.__hoehe = 0 self.__tiefe = 0 self.__vol = 0 def setBreite(self, breite): if self.__breite != breite: self.__breite = breite self.__vol = self.__breite*self.__hoehe*self.__tiefe ... def getVolumen(self): return self.__volNoch effizienter wird es, wenn sich das Objekt merkt, ob ein Parameter inzwischen geändert wurde und erst dann das Volumen berechnet wird, wenn es wirklich angefordert wird. Dazu könnte das Attribut für das Volumen bei jeder Dimensionsänderung auf den Wert -1 gesetzt werden. Berechnet wird nun wieder in der Methode getVolumen, aber nur noch dann, wenn das Volumen -1 ist.
class Kiste: ... def __init__(self): self.__vol = -1 ... def setBreite(self, breite): if self.__breite != breite: self.__breite = breite self.__vol = -1 def setHoehe(self, hoehe): if self.__hoehe != hoehe: self.__hoehe = hoehe self.__vol = -1 def setTiefe(self, tiefe): if self.__tiefe != tiefe: self.__tiefe = tiefe self.__vol = -1 def getVolumen(self): if (self.__vol == -1): print("calc") self.__vol = self.__breite*self.__hoehe*self.__tiefe return self.__volZur Kontrolle meldet die Berechnungsmethode ihre Tätigkeit auf dem Bildschirm. So kann man erkennen, wann berechnet wird und dass die Berechnung nicht unnötig ausgeführt wurde.
kiste = Kiste() kiste.setBreite(2) kiste.setHoehe(3) kiste.setTiefe(4) print(kiste.getVolumen()) print(kiste.getVolumen()) kiste.setHoehe(3) print(kiste.getVolumen()) kiste.setBreite(1) kiste.setTiefe(2) print(kiste.getVolumen())
Setter und Getter hinter property verstecken
Der Zugriff auf die Attribute über Setter- und Getter-Funktionen sieht nicht besonders elegant aus, hat aber den Vorteil, dass das Programm die Kontrolle behält, wenn das Attribut verändert wird.Die Funktionen setBreite() und getBreite() sind nicht so schön schlank wie der einfache Zugriff auf ein Attribut breite.
Python ermöglicht einen anscheinenden Zugriff auf die Attribute, der aber in Wirklichkeit auf Getter beziehungsweise Setter umgeleitet wird. Dazu wird unter dem Namen des Attributs eine property erstellt:
class EindimensionaleKiste: def __init__(self): self.__breite = 0 def setBreite(self, breite): # ... def getBreite(self): # ... breite = property(getBreite, setBreite) kiste = EindimensionaleKiste() kiste.breite = 5 print(kiste.breite)Python erkennt, dass beim ersten Zugriff die Variable breite links von der Zuweisung steht, und ruft darum die Funktion setBreite() auf. Beim zweiten Zugriff wird breite ausgewertet und darum wird automatisch die passende get-Funktion aufgerufen.
Statische Klassen-Variablen
Eine Variable innerhalb einer Klassendefinition bezieht sich normalerweise auf das Objekt, das von der Klasse gebildet wird. Das ist naheliegend, weil jede Kiste ihre eigene Breite hat oder zumindest haben kann.Wenn wir aber wissen wollen, wie viele Kisten es gibt, dann müsste man einen solchen Zähler an die Klasse binden. Eine solche Variable, die es nur einmal für die gesamte Klasse gibt, bezeichnet man als statische Variable oder Klassenvariable.
Statische Variablen werden durch den Zugriff über den Namen der Klasse erreicht. Entsprechend würde man also auf Kiste.anzahl zugreifen. Das folgende Listing zeigt, wie die erstellten Kisten gezählt werden:
class Kiste: anzahl = 0 def __init__(self): self.breite = 0 self.hoehe = 0 self.tiefe = 0 Kiste.anzahl += 1Die Variable anzahl kann als Klassenvariable nur über den Klassennamen angesprochen werden. Verwendet eine Methode einfach den Namen anzahl, so wird ein Attribut self.anzahl angesprochen. Will die Methode dagegen die Klassenvariable verändern, verwendet auch sie den Klassennamen, also Kiste.anzahl.
class Kiste: anzahl = 0 ... def aendere(self): Kiste.anzahl = 100 self.breite = 100 k = Kiste() k.aendere()Soll die Klassenvariable von außen zugegriffen werden, gelingt das nicht über ein Objekt, sondern ebenfalls über den Klassennamen.
Man kann auch Klassenmethoden definieren. Diese unterscheiden sich am auffälligsten von den Objektmethoden dadurch, dass sie keinen Parameter self haben. Da ihnen der Objektbezug durch self fehlt, können sie nicht auf Objektattribute zugreifen, aber auf Klassenattribute.
Der Aufruf solch einer Klassenmethode ist ebenfalls nur über den Klassennamen und nicht über das Objekt möglich, weil ansonsten der Interpreter den Parameter self vermisst.
class Kiste: anzahl = 0 ... def aendereKlassVar(): Kiste.anzahl = 3 k = Kiste() Kiste.aendereKlassVar() #k.aendereKlassVar() # Zugriff verbotenDer Aufruf der Klassenmethode über ein Objekt ist nur erlaubt, wenn die Methode als staticmethod explizit deklariert wird. Ansonsten vermisst der Interpreter den Parameter self.
class Kiste: anzahl = 0 ... def aendereKlassVar(): Kiste.anzahl = 3 k = Kiste() Kiste.aendereKlassVar() #k.aendereKlassVar() # Zugriff verbotenStatische Funktionen können auf statische Variablen zugreifen, aber nicht auf normale Member-Attribute, da sie zu keinem Objekt gehören. Sie müssen mit der Deklaration als staticmethod angemeldet werden.
class Kiste: anzahl = 0 ... def aendereKlassVar(): Kiste.anzahl = 3 aendereKlassVar = staticmethod(aendereKlassVar) k = Kiste() Kiste.aendereKlassVar() k.aendereKlassVar() # Zugriff erlaubt