OOP - Property
Inhalt
OOP - Property¶
Properties¶
Bemerkung
In vielen objektorientierten Sprachen wie Java und C# besitzen private Objektattribute die von außen nicht direkt zugänglich sind. Es müssen oft Getter und Setter Methoden geschrieben werden um auf solche privaten Attribute zugreifen zu können. Statt Methoden stellen einige Programmiersprachen sogenannte Properties zur Verfügung.
In Python sind jedoch alle Attribute und Methoden öffentlich, daher ist es eigentlich nicht richtig von private, protected und public zu sprechen.
Auch auf Attribute mit doppelten Unterstrich wie etwa __name
kann zugegriffen werden.
Python ist jedoch eine Sprache mit viel Konventionen an die sich Entwickler halten.
private, protected und public ?¶
In einigen Büchern und Onlinequellen kann lesen
self.x
sei publicself._x
sei protectedself.__x
sei private
Das ist aber nur eine Konvention an die sich Entwickler halten und keine Eigenschaft von Python. Auch die Unterscheidung von protected und private kann als falsch angesehen werden, da die meisten Entwickler einen einfachen Unterstrich für ihre „private“ Attribute verwenden.
Wir wollen uns diesen Sachverhalt nun ansehen. Dazu implementieren wir eine Klasse mit drei Variablen.
class A():
def __init__(self):
self.x = 1 # public ?
self._x = 2 # protected ?
self.__x = 3 # private ?
Die Variablen werden dabei in einem Dict gespeichert und können mit __dict__
angezeigt werden.
a = A()
print(a.__dict__)
{'x': 1, '_x': 2, '_A__x': 3}
Schon jetzt dürfte klar sein, dass es sich hier nicht um eine private
Variable handelt. Es kann ja ganz einfach auf das Dict zugegriffen werden.
print(a.__dict__['_A__x'])
3
Um die Sache noch deutlicher zu machen werden wir nun alle Werte lesen und schreiben.
a = A()
print(a.x)
print(a._x)
print(a._A__x)
a.x = -1
a._x = -2
a._A__x = -3
print(a.x)
print(a._x)
print(a._A__x)
1
2
3
-1
-2
-3
Python besitz also kein Datenschutzmodel wie C++, Java oder C#. Das betrifft nicht nur Variable sondern auch Methoden.
class A():
def __init__(self):
self.x = 1
self._x = 2
self.__x = 3
def xprint(self):
print(self.x)
def _xprint(self):
print(self._x)
def __xprint(self):
print(self.__x)
a = A()
a = A()
a.xprint()
a._xprint()
a._A__xprint()
1
2
3
Konvention¶
Oft ist es aber notwendig Werte auf einen Gültigkeitsbereich zu überprüfen. Dem Programmierer sollen Schnittstellen angeboten werden um Daten an ein Objekt zu übergeben. Sind die Überprüfungen und Manipulation abgeschlossen soll der gültige Wert in eine interne Variable gespeichert werden. Diese interne Variable wird dann für die Weiterverarbeitung verwendet.
class A():
def __init__(self,x):
self._x = None # "private" attribute
self.set_x(x) # call setter method
def get_x(self):
return self._x
def set_x(self,x):
if (isinstance(x,int)):
if x>=0 and x<=100:
self._x = x
else:
raise ValueError("False Range")
else:
raise ValueError("x should be a <int>")
a = A(20)
a.set_x(50)
a.get_x()
50
Um die Variable zu lesen und zu schreiben sind aber jetzt 2 Funktionen get_x()
und set_x()
nötig. Das schein unnötig kompliziert. Um den Variablenzugriff zu vereinfachen können wir @property
verwenden.
class A():
def __init__(self,x):
self._x = None
self.x = x
def get_x(self):
return self._x
def set_x(self,x):
if (isinstance(x,int)):
if x>=0 and x<=100:
self._x = x
else:
raise ValueError("False Range")
else:
raise ValueError("x should be a <int>")
x = property(get_x,set_x)
a = A(20)
a.x
20
a = A(20)
a.x = 50
a.x
50
Nun können wir sehr einfach auf Instanzattribute zugreifen. Dieses Programm ist zweifellos korrekt. Es gibt allerdings einen Weg der üblicher begangen wird.
a = A()
a.x
0
a = A(20)
a.x = 50
a.x
50
Dieser Code ist jetzt deutlich einfacher zu lesen
a = A(50)
a.x
50
Warnung
In vielen Tutorial sieht man folgenden Code:
class A():
def __init__(self,x):
self._x = x # "private" attribute initialized with x, it does not call the @x.setter
Dieser Code kann aber zu einem Fehler führen, da der @x.setter nicht aufgerufen wird.
Tipp
Wir müssen in dern __init__
Methode auf jeden Fall self.x = x
hinzufügen, weil nur so der @x.setter auch ausgeführt und die Überprüfungen durchgeführt werden.
class A():
def __init__(self,x):
self._x = None # "private" attribute initialized with None,
# it does not call the @x.setter
self.x = x # property and calls the @x.setter
Die Codezeile self._x = None
ist nicht zwingen erforderlich, entspricht aber in vielen Projekten der Konvention und wird auch von machen IDE’s verlangt damit keine Warnungen erzeugt werden. Weiters ist der Code auch besser lesbar und durch @x.setter wird self._x
in jedem Fall dem Objekt hinzugefügt.
Property mit Doppelten Unterstrich (__) ?¶
Warnung
In einigen Büchern und Tutorials wird vorgeschlagen _x
durch __x
zu ersetzen, weil dadurch angeblich ein „privates“ Attribute genutzt wird. Wie wir aber schon wissen handelt es sich bei __x
nicht um private Attribute. Die meisten großen Python-Projekte nutzt einen Unterstrich _x
um interne bzw „private“ Attribute zu kennzeichnen.
class A():
def __init__(self,x=0):
self.__x = None
self.x = x
@property
def x(self):
return self.__x
@x.setter
def x(self,x):
if (isinstance(x,int)):
if x>=0 and x<=100:
self.__x = x
else:
raise ValueError("False Range")
else:
raise ValueError("x should be a <int>")
a = A()
a.__dict__
{'_A__x': 0}
Wie oben schon besprochen werden Attribute mit doppelten Unterstrich __x
durch den Python-Mechanismus name mangling in Attribute _A__x
umbenannt.
Beipsiele: „Pythonic Way“¶
Hier wollen wir nun ein paar kleine Klassen anlegen um das richtige Muster kennen zu lernen.
class Person:
def __init__(self, name):
self._name = None # invernal variable, the setter is not called here.
self.name = name # self.name class the @name.setter, variable initialization
@property
def name(self):
return self._name
@name.setter
def name(self,name):
if not (isinstance(name,str)):
raise ValueError("name must be a str")
else:
self._name = name # the internal _varible save the value
p1 = Person('Max')
print(p1.name)
p1.name = 'Jim'
print(p1.name)
Max
Jim
class Number:
def __init__(self, value):
self._value = None # invernal variable, the setter is not called here.
self.value = value # self.value class the @name.setter, variable initialization
@property
def value(self):
return self._value
@value.setter
def value(self,value):
if not (isinstance(value,int)):
raise ValueError("value must be a int")
else:
self._value = value # the internal _varible save the value
n1 = Number(7)
print(n1.value)
n1.value = 7*7
print(n1.value)
7
49