Python Tutorial
- jetzt Python programmieren lernen

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, wieviel 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

Soweit, 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 der Kontostand ab:

aktueller Kontostand: 1325

aktueller Geldbestand der Bank: 325

Die Kontinentalbank leidet quasi 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 on OOP

public standard

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.