Wenn man eine Klasse in Python schreibt kann diese nicht nur Daten / Eigenschaften in Variablen speichern sondern kann auch Methoden und Eigenschaften besitzen welche von außen nicht zugreifbar sein sollen.
Einleitung
In diesem Beitrag möchte ich dir nun zeigen wie du den Zugriff auf Eigenschaften und / oder Methoden deiner Klasse von außen verhinderst.
Für dieses Beispiel verwende ich eine einfache Klasse Auto, welche die Eigenschaften, Marke, Modell und Baujahr besitzt.
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr def typ(self): print(self.marke, self.modell, self.baujahr, sep=",")
Zusätzlich habe ich noch zwei Methoden implementiert, einmal den Konstruktor zum erstellen eines Auto Objektes sowie eine Methode zum formatierten Ausgeben der gespeicherten Daten.
Wir können uns nun wie bekannt ein oder mehrere Objekte erzeugen:
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr def typ(self): print(self.marke, self.modell, self.baujahr, sep=",") auto1 = Auto("VW","Golf",2000) auto1.typ() auto2 = Auto("Audi","RS8",2018) auto2.typ() auto3 = Auto("BMW","3er",2010) auto3.typ()
Wenn wir nun diesen Code ausführen dann erhalten wir folgende Ausgabe:
VW,Golf,2000 Audi,RS8,2018 BMW,3er,2010
Soweit ist das gut.
Nun nehmen wir an wir sind ein Autohaus und wollen unseren Lagerbestand über die Klasse „Auto“ verfolgen.
Am einfachsten wäre es also nun das wir eine zusätzliche Variable / Eigenschaft „lagerbestand“ hinzufügen.
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr self.lagerbestand = 1 def typ(self): print(self.marke, self.modell, self.baujahr, "Lagerbestand =", str(self.lagerbestand))
Ich habe hier zusätzlich den Seperator entfernt da dieser sonst zwischen „Lagerbestand =“ und dem Wert ein Komma setzen würde.
Wir könnten nun jederzeit von außen den Wert „lagerbestand“ beliebig manipulieren.
auto1 = Auto("VW","Golf",2000) auto1.lagerbestand = 9 auto1.typ() auto2 = Auto("Audi","RS8",2018) auto2.lagerbestand = 12 auto2.typ() auto3 = Auto("BMW","3er",2010) auto3.lagerbestand = -1 auto3.typ()
Im letzten Fall haben wir einen Lagerbestand von -1 eingegeben, nach unser zunächst definierten Regel darf aber minimal der Wert 0 gelten.
Es gibt nur 2 Möglichkeiten dem Benutzer unserer Klasse Auto mitzuteilen das dieser die Variable / Eigenschaft „lagerbestand“ nicht selber manipulieren soll.
Konvention
Jede Programmiersprache hat Ihre Regeln wo sich die Entwickler dran halten sollten (nach Möglichkeiten), diese Regeln nennt man auch Konventionen. Eine dieser Regel ist zbsp. das Variablen und Methoden mit einem beginnenden Unterstrich nicht verwendet werden sollten.
D.h. wenn wir nun unsere Klasse Auto so umschreiben das die Variable „lagerbestand“ mit einem Unterstrich beginnt, sollte jeder Programmierer, welcher sich an die Konventionen von Python hält nicht auf diese Variable zugreifen.
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr self._lagerbestand = 1 def typ(self): print(self.marke, self.modell, self.baujahr, "Lagerbestand =", str(self._lagerbestand))
Wir können aber trotzdem noch den Wert für den Lagerbestand manipulieren.
auto1 = Auto("VW","Golf",2000) auto1._lagerbestand = 9 auto1.typ() auto2 = Auto("Audi","RS8",2018) auto2._lagerbestand = 12 auto2.typ() auto3 = Auto("BMW","3er",2010) auto3._lagerbestand = -1 auto3.typ()
VW Golf 2000 Lagerbestand = 9 Audi RS8 2018 Lagerbestand = 12 BMW 3er 2010 Lagerbestand = -1
Da sich nicht alle Programmierer an diese Regeln halten, sollten wir also sicherstellen das dieser Wert korrekt manipuliert werden kann.
Dazu schreiben wir uns zwei kleine Hilfsmethoden:
def increase_lagerbestand(self): self._lagerbestand = self._lagerbestand + 1 def decrease_lagerbestand(self): if self._lagerbestand >0: self._lagerbestand = self._lagerbestand - 1
Die Methode „increase_lagerbestand“ erhöht den Lagerbestand um 1 und die Methode „decrease_lagerbestand“ verringert den Lagerbestand um 1, aber nur wenn der Lagerbestand größer als 0 ist.
auto1 = Auto("VW","Golf",2000) auto1.increase_lagerbestand() auto1.increase_lagerbestand() auto1.increase_lagerbestand() auto1.typ() auto1.decrease_lagerbestand() auto1.decrease_lagerbestand() auto1.typ() auto2 = Auto("Audi","RS8",2018) auto2.increase_lagerbestand() auto2.decrease_lagerbestand() auto2.increase_lagerbestand() auto2.typ() auto3 = Auto("BMW","3er",2010) auto3._lagerbestand = -1 auto3.typ() auto3.decrease_lagerbestand() auto3.typ()
Im ersten Auto Objekt haben 3 mal den Lagerbestand um 1 erhöht, somit ist der Lagerbestand 4 (Startwert ist 1). Danach rufen wir 2 mal die Methode „decrease_lagerbestand“ auf und verringern somit den Lagerbestand um 1. Ähnlich verläuft dieses beim zweiten Auto Objekt.
Im dritten Auto Objekt jedoch greifen wir zunächst auf den Lagerbestand per Variable zu und setzen diesen auf den Wert -1 wenn wir nun den Lagerbestand mit der Methode „decrease_lagerbestand“ verringern wird dieses nicht gemacht da der Wert kleiner als 0 ist.
VW Golf 2000 Lagerbestand = 4 VW Golf 2000 Lagerbestand = 2 Audi RS8 2018 Lagerbestand = 2 BMW 3er 2010 Lagerbestand = -1 BMW 3er 2010 Lagerbestand = -1
Nun bleibt eine Frage offen: Wie können wir wirklich sicherstellen das die Eigenschaft „lagerbestand“ von außen nicht manipuliert werden kann? Und dieses wollen wir im nächsten Abschnitt durchführen.
definieren einer privaten Variable
Zunächst haben wir darauf gesetzt das der Programmierer die Regeln und / oder Konventionen von Python einhält. In diesem Abschnitt möchte ich nun aufzeigen wie einfach es ist eine private Variable / Methode zu erstellen.
Im ersten Abschnitt habe ich einen (1) Unterstrich vor die Variable „lagerbestand“ gesetzt, um nun aus dieser Variable eine private Variable zu machen benötigen wir zwei (2) Unterstriche vor der Variablen.
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr self.__lagerbestand = 1 def typ(self): print(self.marke, self.modell, self.baujahr, "Lagerbestand =", str(self.__lagerbestand)) def increase_lagerbestand(self): self.__lagerbestand = self.__lagerbestand + 1 def decrease_lagerbestand(self): if self.__lagerbestand >0: self.__lagerbestand = self.__lagerbestand - 1
Wenn wir also versuchen die nun private Variable „__lagerbestand“ wie folgt zu manipulieren.
auto3 = Auto("BMW","3er",2010) auto3.__lagerbestand = -8 auto3.typ() auto3.decrease_lagerbestand() auto3.typ()
Dann sieht die Ausgabe wie folgt aus:
BMW 3er 2010 Lagerbestand = 1 BMW 3er 2010 Lagerbestand = 0 -8
Aber warum?
Wir haben in diesem Fall unserem dritten Auto Objekt eine neue Eigenschaft „__lagerbestand“ hinzugefügt welche die eigentliche Variable im originalen Auto Objekt nicht kennt (diese ist ja privat). Also können wir diese auch manipulieren und ausgeben. Die beiden Methoden „increase_lagerbestand“ und „decrease_lagerbestand“ arbeiten jedoch noch mit der originalen internen Variable.
Wenn wir also stattdessen folgendes schreiben würden:
class Auto(): def __init__(self, marke, modell, baujahr): self.marke = marke self.modell = modell self.baujahr = baujahr self.__lagerbestand = 1 def typ(self): print(self.marke, self.modell, self.baujahr, "Lagerbestand =", str(self.__lagerbestand)) def increase_lagerbestand(self): self.__lagerbestand = self.__lagerbestand + 1 def decrease_lagerbestand(self): if self.__lagerbestand >0: self.__lagerbestand = self.__lagerbestand - 1 auto3 = Auto("BMW","3er",2010) auto3.typ() auto3.decrease_lagerbestand() auto3.typ() print(auto3.__lagerbestand)
So erhalten wir eine Fehlermeldung dass, die Variable „__lagerbestand“ unbekannt ist.
BMW 3er 2010 Lagerbestand = 1 BMW 3er 2010 Lagerbestand = 0 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-34-f6a00e1ea387> in <module>() 21 auto3.typ() 22 ---> 23 print(auto3.__lagerbestand) AttributeError: 'Auto' object has no attribute '__lagerbestand'
private Methoden
Was mit einer Variablen funktioniert kann auch genauso mit einer Methode gemacht werden. D.h. um eine Methode privat zu machen setzen wir zwei Unterstriche vor diese Methode.
Nehmen wir an wir würden einen Nachlass für den Verkauf eines Autos berechnen wollen, diese Berechnung soll natürlich geheim bleiben.
class Auto(): def __init__(self, marke, modell, baujahr, preis): self.marke = marke self.modell = modell self.baujahr = baujahr self.preis = preis self.__nachlassPercent = 5 self.__lagerbestand = 1 def typ(self): print(self.marke, self.modell, self.baujahr, self.preis, "€", "Lagerbestand =", str(self.__lagerbestand)) def increase_lagerbestand(self): self.__lagerbestand = self.__lagerbestand + 1 def decrease_lagerbestand(self): if self.__lagerbestand >0: self.__lagerbestand = self.__lagerbestand - 1 def verkaufe(self): if self.__lagerbestand > 0: self.decrease_lagerbestand() print("Verkaufspreis: "+str(self.preis - self.calc_nachlass())+"€") def calc_nachlass(self): return (self.preis / 100) * self.__nachlassPercent
Wir verwenden in der Methode „calc_nachlass“ eine einfache Prozentrechnung wo wir z.Zt. den definierten Prozentwert von 5% vom Verkaufspreis abziehen.
Wir könnten also nun auf dem Auto Objekt die Methode „calc_nachlass“ ausführen, das wollen wir verhindern denn diese Methode soll nur intern verwendet werden. Wir setzen also nun zwei Unterstriche davor und somit können wir diese Methode nicht mehr von außen zugreifen.
def __calc_nachlass(self): return (self.preis / 100) * self.__nachlassPercent
auto3 = Auto("BMW","3er",2010, 9600) auto3.typ() auto3.verkaufe() auto3.calc_nachlass()
BMW 3er 2010 9600 € Lagerbestand = 1 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-58-5797a46d3cf6> in <module>() 32 auto3 = Auto("BMW","3er",2010, 9600) 33 auto3.typ() ---> 34 auto3.verkaufe() 35 auto3.calc_nachlass() 36 <ipython-input-58-5797a46d3cf6> in verkaufe(self) 22 if self.__lagerbestand > 0: 23 self.decrease_lagerbestand() ---> 24 print("Verkaufspreis: "+str(self.preis - self.calc_nachlass())+"€") 25 26 def __calc_nachlass(self): AttributeError: 'Auto' object has no attribute 'calc_nachlass'
Um jetzt jedoch trotzdem den Nachlass zu manipulieren schreiben wir uns eine kleine Setter Methode und können so trotzdem von außen den Wert ändern.
def set_nachlassPercent(self, perc): self.__nachlassPercent = perc
auto3 = Auto("BMW","3er",2010, 9600) auto3.typ() auto3.set_nachlassPercent(23) auto3.verkaufe()
BMW 3er 2010 9600 € Lagerbestand = 1 Verkaufspreis: 7392.0€