Wir haben gelernt, dass einer der wesentlichen Vorteile von Objektorientierung die Datenkapselung ist. Damit ist gemeint, dass der Zugriff auf Eigenschaften und Methoden eingeschränkt werden kann. Manche Programmiersprachen wie z.B. Java markieren diese Zugriffsrechte explizit und sind in der Auslegung sehr strikt. Diese Variablendeklaration in Java beschränkt den Zugriff auf eine Variable auf die Klasse selbst:
private int score = 0;
Dadurch kann der Wert von score
nur aus der Klasse heraus gelesen oder verändert werden.
public String username;
Hingegegen erlaubt den uneingeschränkten Zugriff auf die Eigenschaft username
.
Diesen Mechanismus gibt es auch in Python, allerdings geht man hier die Dinge relaxter an: Ein vor einen Variablennamen oder einen Methodennamen gesetztes Underline bedeutet, dass dieser Teil des Objekt von außerhalb des Objekts nicht verwendet, vor allem nicht verändert werden soll.
In [ ]:
class MyClass:
def __init__(self, val):
self.set_val(val)
def get_val(self):
return self._val
def set_val(self, val):
if val > 0:
self._val = val
else:
raise ValueError('val must be greater 0')
myclass = MyClass(27)
myclass._val
Wie wir sehen, ist die Eigenschaft _val
durchaus von außerhalb verfügbar. Allerdings signalisiert das Underline, dass vom Programmierer der Klasse nicht vorgesehen ist, dass dieser Wert direkt verwendet wird (sondern z.B. nur über die Methoden get_val()
und set_val()
). Wenn ein anderer Programmierer der Meinung ist, dass er direkten Zugriff auf die Eigenschaft _val
braucht, liegt das in seiner Verantwortung (wird aber von Python nicht unterbunden). Man spricht hier von protection by convention. Python-Programmierer halten sich in aller Regel an diese Konvention, weshalb dieser Art von "Schutz" weit verbreitet ist.
In [ ]:
class MyClass:
def __init__(self, val):
self.__val = val
myclass = MyClass(42)
myclass.__val
Hier sehen wir, dass die Eigenschaft __val
von außerhalb der Klasse gar nicht sichtbar und damit auch nicht veränderbar ist. Innerhalb der Klasse ist sie jedoch normal verfügbar. Das kann zu Problemen führen:
In [ ]:
class MySpecialClass(MyClass):
def get_val(self):
return self.__val
msc = MySpecialClass(42)
msc.get_val()
Da __val
nur innerhalb der Basisklasse angelegt wurde, hat die abgeleitete Klasse keinen Zugriff darauf.
Wie wir gesehen haben, werden für den Zugriff auf geschützte Eigenschaften eigene Getter- und Setter-Methoden geschrieben, über die der Wert einer Eigenschaft kontrolliert verändert werden kann. Programmieren wir eine Student
-Klasse, in der eine Note gespeichert werden soll. Um den Zugriff auf diese Eigenschaft zu kontrollieren, schreiben wir eine Setter- und eine Getter-Methode.
In [ ]:
class GradingError(Exception): pass
class Student:
def __init__(self, matrikelnr):
self.matrikelnr = matrikelnr
self._grade = 0
def set_grade(self, grade):
if grade > 0 and grade < 6:
self._grade = grade
else:
raise ValueError('Grade must be between 1 and 5!')
def get_grade(self):
if self._grade > 0:
return self._grade
raise GradingError('Noch nicht benotet!')
Wir können jetzt die Note setzen und auslesen:
In [ ]:
anna = Student('01754645')
anna.set_grade(6)
In [ ]:
anna.set_grade(2)
anna.get_grade()
Allerdings ist der direkte Zugriff auf grade
immer noch möglich:
In [ ]:
anna._grade
In [ ]:
anna._grade = 6
Wie wir bereits gesehen haben, können wir das verhindern, indem wir die Eigenschaft grade
auf __grade
umbenennen.
In [ ]:
class Student:
def __init__(self, matrikelnr):
self.matrikelnr = matrikelnr
self.__grade = 0
def set_grade(self, grade):
if grade > 0 and grade < 6:
self.__grade = grade
else:
raise ValueError('Grade must be between 1 and 5!')
def get_grade(self):
if self.__grade > 0:
return self.__grade
raise GradingError('Noch nicht benotet!')
grade = property(get_grade, set_grade)
otto = Student('01745646465')
otto.grade = 6
Wie wir sehen, können wir die Eigenschaft des Objekts direkt setzen und auslesen, der Zugriff wird aber von Python jeweils durch den Setter und Getter geleitet.
Wenn wir nur eine Methode (den Getter) als Argument an die property()-Funktion übergeben, haben wir eine Eigenschaft, die sich nur auslesen, aber nicht verändern lässt.
In [ ]:
class Student:
def __init__(self, matrikelnr, grade):
self.matrikelnr = matrikelnr
self.__grade = grade
def get_grade(self):
if self.__grade > 0:
return self.__grade
raise GradingError('Noch nicht benotet!')
grade = property(get_grade)
albert = Student('0157897846546', 5)
albert.grade
Wir können also auf unsere via property() definierte Eigenschaften zugreifen. Wir können grade
aber nicht verwenden,
um die Eigenschaft zu verändern:
In [ ]:
albert.grade = 1
Dekoratoren erweitern dynamisch die Funktionalität von Funktionen indem sie diese (im Hintergrund) in eine weitere Funktion verpacken. Die Anwendung eines Dekorators ist einfach: man schreibt ihn einfach vor die Funktionsdefinition.
Python bringt eine Reihe von Dekoratoren mit, man kann sich aber auch eigene Dekoratoren schreiben, was jedoch hier nicht behandelt wird.
Der in Python eingebaute @property
-Dekorator ist eine Alternative zu der oben vorgestellten property()
-Funktion:
In [ ]:
class Student:
def __init__(self, matrikelnr):
self.matrikelnr = matrikelnr
self.__grade = 0
@property
def grade(self):
if self.__grade > 0:
return self.__grade
raise GradingError('Noch nicht benotet!')
@grade.setter
def grade(self, grade):
if grade > 0 and grade < 6:
self.__grade = grade
else:
raise ValueError('Grade must be between 1 and 5!')
hugo = Student('0176464645454')
In [ ]:
hugo.grade = 6
In [ ]:
hugo.grade = 2
In [ ]:
hugo.grade
In [ ]:
class MyClass:
the_answer = 42
def __init__(self, val):
self.the_answer = val
MyClass.the_answer
In [ ]:
mc = MyClass(17)
print('Objekteigenschaft:', mc.the_answer)
print('Klasseneigenschaft:', MyClass.the_answer)
Die eine Eigenschaft hängt also am Klassenobjekt, die andere am aus der Klasse erzeugten Objekt. Solche Klassenobjekte können nützlich sein, weil sie in allen aus der Klasse erzeugten Objekten verfügbar sind (sogar via self
, solange das Objekt nicht selbst eine gleichnamige Eigenschaft hat:
In [ ]:
class MyClass:
instance_counter = 0
def __init__(self):
MyClass.instance_counter += 1
print('Ich bin das {}. Objekt'.format(MyClass.instance_counter))
a = MyClass()
b = MyClass()
In [ ]:
class MyOtherClass(MyClass):
instance_counter = 0
a = MyOtherClass()
b = MyOtherClass()
Man kann das auch so schreiben, wodurch der Counter auch für Subklassen funktioniert:
In [ ]:
class MyClass:
instance_counter = 0
def __init__(self):
self.__class__.instance_counter += 1
print('Ich bin das {}. Objekt'.format(self.__class__.instance_counter))
a = MyClass()
b = MyClass()
In [ ]:
class MyOtherClass(MyClass):
instance_counter = 0
a = MyOtherClass()
b = MyOtherClass()
In [ ]: