| 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.__vol
Nun 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.__vol
Noch 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.__vol
Zur 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 += 1
Die 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 verboten
Der 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 verboten
Statische 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