Eigenschaften vor Zugriff absichern
Im letzten Kapitel haben wir gesehen, wie einfach wir von außen auf Variablen (und auch Eigenschaften) zugreifen und diese beliebig ändern können.
Dies ist je nach Anwendung fatal!
Nehmen wir an, wir sind eine Bank (die fiktive Kontinentalbank) und verwalten Konten und Geld für unsere Kunden.
Also brauchen wir eine Klasse, die sowohl Konten (mit einer Kontonummer) und aktuelles Guthaben verwaltet.
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
def __init__(self, kontonummer, kontostand=0):
self.kontonummer = kontonummer
self.kontostand = kontostand
def geld_abheben(self, betrag):
self.kontostand -= betrag
def geld_einzahlen(self, betrag):
self.kontostand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.kontostand)
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
Jetzt kann der Kunde Geld einzahlen:
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(150)
kunde_schulz.kontostand_anzeigen()
Bei Banken und Buchführung gibt es immer doppelte Buchführung – es darf nichts nicht nachvollziehbar sein. Wir realisieren dies in kleiner Form, in dem wir (wie im letzten Kapitel schon gezeigt) eine Bestandszahl mitführen, wie viel Geld insgesamt gerade in der Bank sein müsste und geben Einzahlungen, Auszahlungen und den Gesamtbestand aus:
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
geldbestand = 0
def __init__(self, kontonummer, kontostand=0):
self.kontonummer = kontonummer
self.kontostand = kontostand
def geld_abheben(self, betrag):
print("Geld wird abgehoben:", betrag)
self.kontostand -= betrag
Konto.geldbestand -= betrag
def geld_einzahlen(self, betrag):
print("Geld wird eingezahlt:", betrag)
self.kontostand += betrag
Konto.geldbestand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.kontostand)
print("aktueller Geldbestand der Bank: ", Konto.geldbestand, "\n")
Wir lassen den Kunden Schulz 2-mal einzahlen und einmal abheben:
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(150)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(250)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_abheben(75)
kunde_schulz.kontostand_anzeigen()
Als Ergebnis erhalten wir:
aktueller Kontostand: 0
aktueller Geldbestand der Bank: 0
Geld wird eingezahlt: 150
aktueller Kontostand: 150
aktueller Geldbestand der Bank: 150
Geld wird eingezahlt: 250
aktueller Kontostand: 400
aktueller Geldbestand der Bank: 400
Geld wird abgehoben: 75
aktueller Kontostand: 325
aktueller Geldbestand der Bank: 325
So weit, so gut. Die Rechnung passt: 150 + 250 -75 = 325 (was auch der aktuelle Geldbestand der Bank zeigt).
Was passiert aber, wenn unser Kunde ein Schlitzohr ist? Er betätigt sich einfach eines direkten Zugriffs. Vor dem letzten Abheben erhöht er einfach einmal sein Kontostand um 1000.
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(150)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(250)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.kontostand += 1000
kunde_schulz.geld_abheben(75)
kunde_schulz.kontostand_anzeigen()
Und schon haben wir ein massives Problem – die Kontrollzahl weicht von dem Kontostand ab:
aktueller Kontostand: 1325
aktueller Geldbestand der Bank: 325
Die Kontinentalbank leidet gewissermaßen an „Inkontinenz“, Geld verrinnt.
Das müssen wir verhindern!
Unser bisher kritischer Code komplett:
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
geldbestand = 0
def __init__(self, kontonummer, kontostand=0):
self.kontonummer = kontonummer
self.kontostand = kontostand
def geld_abheben(self, betrag):
print("Geld wird abgehoben:", betrag)
self.kontostand -= betrag
Konto.geldbestand -= betrag
def geld_einzahlen(self, betrag):
print("Geld wird eingezahlt:", betrag)
self.kontostand += betrag
Konto.geldbestand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.kontostand)
print("aktueller Geldbestand der Bank: ", Konto.geldbestand, "\n")
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(150)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(250)
kunde_schulz.kontostand_anzeigen()
kunde_schulz.kontostand += 1000
kunde_schulz.geld_abheben(75)
kunde_schulz.kontostand_anzeigen()
Eigenschaften (und auch Variablen) dürfen nicht von außen veränderbar sein. Auch hier hilft uns Python. Wir können die Sichtbarkeit von Eigenschaften und Variablen einschränken:
Sichtbarkeit von Eigenschaften/Variablen in der OOP
Wir können die „Nutzbarkeit“ von Variablen einschränken über folgende Schreibweisen:
public | standard, kann auch von Außerhalb der Klassen genutzt und geändert werden |
protect | Schreibweise: _1Unterstrichamanfang kann in eigener Klasse und davon abgeleiteten Klassen verwendet werden |
privat | Schreibweise: __2Unterstricheamanfang kann nur in innerhalb eigener Klasse genutzt werden |
Das bedeutet, dass wir unsere Eigenschaften und Variablen als privat auszeichnen müssen.
Weil wir ein sicherheitsbewusster Programmierer sind, werden alle verwendeten Eigenschaften und Variablen als „privat“ mit den 2 Unterstrichen am Anfang gekennzeichnet.
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
__geldbestand = 0
def __init__(self, kontonummer, kontostand=0):
self.__kontonummer = kontonummer
self.__kontostand = kontostand
def geld_abheben(self, betrag):
print("Geld wird abgehoben:", betrag)
self.__kontostand -= betrag
Konto.__geldbestand -= betrag
def geld_einzahlen(self, betrag):
print("Geld wird eingezahlt:", betrag)
self.__kontostand += betrag
Konto.__geldbestand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.__kontostand)
print("aktueller Geldbestand der Bank: ", Konto.__geldbestand, "\n")
Ab jetzt ist weder das direkte Abfragen von Variablen/Eigenschaften noch das Setzen von Werten außerhalb der Klasse möglich. Ein Versuch endet mit einer Fehlermeldung:
kunde_schulz = Konto("000111555")
kunde_schulz.kontostand_anzeigen()
kunde_schulz.geld_einzahlen(150)
kunde_schulz.__kontostand += 1000
Es kommt als Fehlermeldung: „AttributeError: 'Konto' object has no attribute '__kontostand'“
Wir können weder etwas verändern noch abfragen von außen.
Bank gerettet!
Was passiert, wenn wir mit einer Kindklasse auf die geschützten Werte zugreifen wollen?
Zugriff auf geschützte Eigenschaften über Kindklasse
Wir wollen eine Klasse erstellen, welche die Konto-Klasse beerbt. Wann kann das erforderlich sein? Seit ein paar Jahre gibt es das sogenannte „Guthabenkonto“. Diese wurde für insolvente bzw. überschuldete Menschen eingeführt und bei diesen Konten gibt es nicht die Möglichkeit der Überziehung.
Diese ist für uns natürlich die ideale Übung als Kindeklasse eines normalen Kontos.
Auszahlen finden nur statt, wenn der gewählte Betrag nicht das Konto in das Minus treiben würde.
Allerdings kommt nun als Erschwerung dazu, dass wir nicht mehr so einfach Zugriff auf alle Werte und Eigenschaften der Klasse „Konto“ haben, da diese als „__privat“ gekennzeichnet sind.
Aber einen Schritt nach dem anderen!
Wir erstellen unsere Kindklasse „Pluskonto“:
class Pluskonto(Konto):
""" ein Konto, dass nicht überzogen werden kann """
def __init__(self, kontonummer, kontostand=0):
""" Initalisieren über Eltern-Klasse """
super().__init__(kontonummer, kontostand=0)
def geld_abheben(self, betrag):
print("Geld soll vom Pluskonto abgehoben werden:", betrag)
Jetzt wollen wir auf den Kontostand der Elternklasse „Konto“ zugreifen. Was bisher einfach funktionierte, geht durch die __privat gesetzten Eigenschaften nicht mehr. Aber wir benötigen ja nur den Wert um vergleichen zu können, ob das Konto ins Minus gehen würde und wollen diesen nicht verändert.
Also programmieren wir uns eine Methode in der Elternklasse, die uns diesen Wert liefert:
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
__geldbestand = 0
def __init__(self, kontonummer, kontostand=0):
self.__kontonummer = kontonummer
self.__kontostand = kontostand
def geld_abheben(self, betrag):
print("Geld wird abgehoben:", betrag)
self.__kontostand -= betrag
Konto.__geldbestand -= betrag
def geld_einzahlen(self, betrag):
print("Geld wird eingezahlt:", betrag)
self.__kontostand += betrag
Konto.__geldbestand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.__kontostand)
print("aktueller Geldbestand der Bank: ", Konto.__geldbestand, "\n")
def kontostand_aktuell(self):
return self.__kontostand
Die letzte Methode kontostand_aktuell(self)
liefert uns den aktuellen Wert über return
zurück:
Auf diesen können wir in der Kindklasse „Pluskonto“ zugreifen und zum Vergleich heranziehen:
def geld_abheben(self, betrag):
print("Geld soll vom Pluskonto abgehoben werden:", betrag)
print("Maximal verfügbar ist gerade:", self.kontostand_aktuell())
if self.kontostand_aktuell() - betrag >= 0:
print("Auszahlen von Pluskonto: ", betrag)
else:
print("Sorry, Konto kann nicht überzogen werden!")
Jetzt müssen wir noch das Geld verbuchen!
Wir können von der Kindklasse auf die Methoden der Elternklasse über super().geld_abheben()
zugreifen. Dies ergänzen wir innerhalb unserer if-Abfrage.
def geld_abheben(self, betrag):
print("Geld soll vom Pluskonto abgehoben werden:", betrag)
print("Maximal verfügbar ist gerade:", self.kontostand_aktuell())
if self.kontostand_aktuell() - betrag >= 0:
print("Auszahlen von Pluskonto: ", betrag)
super().geld_abheben(betrag)
else:
print("Sorry, Konto kann nicht überzogen werden!")
Der vollständige Code:
class Konto:
""" unsere kleines Bankprogramm zum Verwalten Konten/Geld """
__geldbestand = 0
def __init__(self, kontonummer, kontostand=0):
self.__kontonummer = kontonummer
self.__kontostand = kontostand
def geld_abheben(self, betrag):
print("Geld wird abgehoben:", betrag)
self.__kontostand -= betrag
Konto.__geldbestand -= betrag
def geld_einzahlen(self, betrag):
print("Geld wird eingezahlt:", betrag)
self.__kontostand += betrag
Konto.__geldbestand += betrag
def kontostand_anzeigen(self):
print("aktueller Kontostand: ", self.__kontostand)
print("aktueller Geldbestand der Bank: ", Konto.__geldbestand, "\n")
def kontostand_aktuell(self):
return self.__kontostand
class Pluskonto(Konto):
""" ein Konto, dass nicht überzogen werden kann """
def __init__(self, kontonummer, kontostand=0):
""" Initalisieren über Eltern-Klasse """
super().__init__(kontonummer, kontostand=0)
def geld_abheben(self, betrag):
print("Geld soll vom Pluskonto abgehoben werden:", betrag)
print("Maximal verfügbar ist gerade:", self.kontostand_aktuell())
if self.kontostand_aktuell() - betrag >= 0:
print("Auszahlen von Pluskonto: ", betrag)
super().geld_abheben(betrag)
else:
print("Sorry, Konto kann nicht überzogen werden!")
kunde_minderjaehrig = Pluskonto("0000935")
kunde_minderjaehrig.kontostand_anzeigen()
kunde_minderjaehrig.geld_einzahlen(200)
kunde_minderjaehrig.geld_abheben(101)
kunde_minderjaehrig.kontostand_anzeigen()
Sicherheit benötigt also ein wenig mehr Arbeit – ist aber problemlos machbar, wenn man weiß wie.