Datenkapselung – public vs. private
Schützen von Attributen
Bisher haben wir die Methode aendere_preis_um(self, aenderung)
noch gar nicht verwendet. Diese Methode wurde mit einer if-Abfrage so programmiert, dass der Preis eines Autos durch eine Änderung nicht negativ werden kann. Dies sehen wir, wenn wir die Methode in Autotest.py
verwenden:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.aendere_preis_um(-50000)
print(mein_erstes_auto.preis) # Ausgabe: 10000
mein_erstes_auto.aendere_preis_um(-20000) # Ausgabe: Die Änderung ist nicht zulässig!
print(mein_erstes_auto.preis) # Ausgabe: 10000
Vielleicht wäre es geschickt, die Methode mache_probefahrt(self, kilometer)
ebenfalls etwas sicherer zu gestalten. Momentan können wir nämlich einfach eine negative Zahl angeben und so den Kilometerstand zurückdrehen:
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.mache_probefahrt(-100)
print(mein_erstes_auto.kilometerstand) # Ausgabe: 53900
Auch hier können wir eine if-Abfrage verwenden:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.baujahr = baujahr
self.marke = marke
self.kilometerstand = kilometerstand
self.automatik = automatik
self.preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.kilometerstand = self.kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.preis+aenderung > 0:
self.preis = self.preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.baujahr) + " " + str(self.kilometerstand)
Wollen wir nun den Kilometerstand zurückdrehen, erscheint eine eindeutige Meldung:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.mache_probefahrt(-100) # Betrugsversuch!
print(mein_erstes_auto) # Ausgabe: 1985 54000
mein_erstes_auto.mache_probefahrt(100)
print(mein_erstes_auto) # Ausgabe: 1985 54100
Es scheint, als sei der Kilometerstand damit vor Manipulationen geschützt. Das ist er jedoch nicht. Anstatt nämlich mithilfe der Methode mache_probefahrt(self, kilometer)
den Kilometerstand zu verändern, können wir einfach direkt auf das Attribut kilometerstand
zugreifen und seinen Wert verändern:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.kilometerstand = 50000
print(mein_erstes_auto) # Ausgabe: 1985 50000
Hier wird der Kilometerstand einfach auf 50000 km zurückgedreht. Die anderen Attribute sind ebenso frei zugänglich, so dass man auch bei diesen die Werte unkontrolliert verändern könnte.
Um einen solch willkürlichen Zugriff auf die Attribute einer Klasse zu unterbinden, können wir diese schützen, indem man sie als private Attribute deklarieren. Dies geschieht mithilfe eines doppelten Unterstrichs:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.__baujahr = baujahr
self.__marke = marke
self.__kilometerstand = kilometerstand
self.__automatik = automatik
self.__preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.__kilometerstand = self.__kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.__preis+aenderung > 0:
self.__preis = self.__preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.__baujahr) + " " + str(self.__kilometerstand)
Nun läuft der Test so ab:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.kilometerstand = 50000
print(mein_erstes_auto) # Ausgabe: 1985 50000
Der Preis wurde also nicht verändert.
Es ist nun vielleicht überraschend, dass gar keine Fehlermeldung auftauchte, als wir versuchten, den Kilometerstand zu ändern. Schließlich sollte das Attribut von außen doch nicht mehr sichtbar sein. Dennoch wird führt die Anweisung mein_erstes_auto.kilometerstand = 50000
zu keinem Fehler.
Das liegt daran, dass Python aufgrund dieser Anweisung unserem Objekt noch ein weiteres öffentliches Attribut kilometerstand
hinzufügt! Davon können wir uns z.B. so überzeugen:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
mein_erstes_auto.kilometerstand = 50000
print(mein_erstes_auto) # Ausgabe: 1985 50000
print(mein_erstes_auto.kilometerstand) # Ausgabe: 50000
Das birgt natürlich die Gefahr von Verwechslungen.
Außerdem können wir nun den Kilometerstand nur noch mittels der Ausgabe durch die str-Methode erfahren, wie man hier sieht:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
print(mein_erstes_auto.kilometerstand) # Ausgabe: AttributeError: 'Auto' object has no attribute 'kilometerstand'
Möchten wir aber den Stand konkret als int-Wert erhalten, ist dies nun recht ungünstig.
Im Folgenden sehen wir, wie wir diese Schwierigkeiten lösen können.
Getter
Um das Auslesen (aber nicht das Verändern) des Wertes eines Attributes zu ermöglichen, müssen wir die sogenannten Getter (oder auch get-Methoden) ergänzen. Hier ist diese erst einmal nur exemplarisch für den Kilometerstand hinzugefügt:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.__baujahr = baujahr
self.__marke = marke
self.__kilometerstand = kilometerstand
self.__automatik = automatik
self.__preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.__kilometerstand = self.__kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.__preis+aenderung > 0:
self.__preis = self.__preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.__baujahr) + " " + str(self.__kilometerstand)
def get_kilometerstand(self):
return self.__kilometerstand
Die Getter sind immer nach dem gleichen Muster aufgebaut. Die einzige Anweisung, die von einem Getter ausgeführt wird, ist das Zurückgeben des Attributwertes mit return
.
Nun können wir die Werte der Attribute von außen auslesen. Die Getter bilden dabei eine Einbahnstraße: Auslesen ist erlaubt, Verändern nicht. Hier ein Beispiel zur Verwendung eines Getters:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
print(mein_erstes_auto.get_kilometerstand()) # Ausgabe: 54000
Setter
Neben den get-Methoden können oft auch set-Methoden nützlich sein. Diese sind – ähnlich wie aendere_preis_um
– dazu da, den Wert einen Attributs kontrolliert zu verändern. Allerdings gibt man dort dann immer direkt den neuen gewünschten Wert an. Hier ein Beispiel für unsere Klasse Auto
:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.__baujahr = baujahr
self.__marke = marke
self.__kilometerstand = kilometerstand
self.__automatik = automatik
self.__preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.__kilometerstand = self.__kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.__preis+aenderung > 0:
self.__preis = self.__preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.__baujahr) + " " + str(self.__kilometerstand)
def get_kilometerstand(self):
return self.__kilometerstand
def set_kilometerstand(self, stand):
if stand > self.__kilometerstand:
self.__kilometerstand = stand
Eine Anwendung sieht dann so aus:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
print(mein_erstes_auto.get_kilometerstand()) # Ausgabe: 54000
mein_erstes_auto.set_kilometerstand(55000)
print(mein_erstes_auto.get_kilometerstand()) # Ausgabe: 55000
Properties
Im Grunde könnten wir nun so mit unseren privaten Attributen und den zugehörigen Gettern und Settern arbeiten. In Python ist es jedoch üblich, die Arbeit ein wenig komfortabler zu gestalten, damit man von außen doch wieder auf die Verwendung von get
bzw. set
verzichten kann, obwohl innerhalb einer Klasse mit diesen gearbeitet wird.
Dazu verwendet man Properties, hier ganz unten für unser Attribut kilometerstand
ergänzt:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.__baujahr = baujahr
self.__marke = marke
self.__kilometerstand = kilometerstand
self.__automatik = automatik
self.__preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.__kilometerstand = self.__kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.__preis+aenderung > 0:
self.__preis = self.__preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.__baujahr) + " " + str(self.__kilometerstand)
def get_kilometerstand(self):
return self.__kilometerstand
def set_kilometerstand(self, stand):
if stand > self.__kilometerstand:
self.__kilometerstand = stand
kilometerstand = property(get_kilometerstand, set_kilometerstand)
Nun kann man von außen scheinbar ohne Umweg auf das Attribut zugreifen. In Wahrheit erden dabei aber der Getter bzw. Setter aufgerufen:
# Datei Auto_Test.py
from Auto import *
mein_erstes_auto = Auto(1985, "DeLorean", 54000, False, 60000)
print(mein_erstes_auto.kilometerstand) # Ausgabe: 54000
mein_erstes_auto.kilometerstand= 55000
print(mein_erstes_auto.kilometerstand) # Ausgabe: 55000
mein_erstes_auto.kilometerstand= 50000
print(mein_erstes_auto.kilometerstand) # Ausgabe: 55000
Der Stand wird nicht wider auf 50000 verringert, woran man sieht, dass tatsächlich der Setter verwendet wurde.
In dieser Version könnten wir nun entweder direkt den Attributnamen verwenden oder aber auch Getter und Setter aufrufen. In Python möchte man aber immer gerne nur einen eindeutigen Weg zur Verfügung stellen, um eine bestimmte Aktion durchzuführen. Daher sollten wir nun in einem letzten Schritt noch Getter und Setter zu privaten Methoden machen:
# Datei Auto.py
class Auto:
def __init__(self, baujahr, marke, kilometerstand, automatik, preis):
self.__baujahr = baujahr
self.__marke = marke
self.__kilometerstand = kilometerstand
self.__automatik = automatik
self.__preis = preis
def mache_probefahrt(self, kilometer):
if kilometer >= 0:
self.__kilometerstand = self.__kilometerstand + kilometer
else:
print("Betrugsversuch!")
def aendere_preis_um(self, aenderung):
if self.__preis+aenderung > 0:
self.__preis = self.__preis + aenderung
else:
print("Die Änderung ist nicht zulässig!")
def __str__(self):
return str(self.__baujahr) + " " + str(self.__kilometerstand)
def __get_kilometerstand(self):
return self.__kilometerstand
def __set_kilometerstand(self, stand):
if stand > self.__kilometerstand:
self.__kilometerstand = stand
kilometerstand = property(__get_kilometerstand, __set_kilometerstand)
Mit den übrigen Attributen sollten wir nun analog verfahren. Das bietet sich hier als kleine Übung an ;)