Was ist der pythonische Weg, um Getter und Setter zu verwenden?
Der "pythonische" Weg ist nicht "Getter" und "Setter" zu verwenden, sondern einfache Attribute zu verwenden, wie die Frage zeigt, und del
zum Löschen (aber die Namen sind geändert, um die Unschuldigen zu schützen... buildins):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
Wenn Sie die Einstellung später ändern wollen, können Sie dies tun, ohne den Benutzercode zu ändern, indem Sie die property
Dekorateur:
class Obj:
"""property demo"""
#
@property # first decorate the getter method
def attribute(self): # This getter method name is *the* name
return self._attribute
#
@attribute.setter # the property decorates with `.setter` now
def attribute(self, value): # name, e.g. "attribute", is the same
self._attribute = value # the "value" name isn't special
#
@attribute.deleter # decorate with `.deleter`
def attribute(self): # again, the method name is the same
del self._attribute
(Jede Dekoratorverwendung kopiert und aktualisiert das vorherige Eigenschaftsobjekt, also beachten Sie, dass Sie den gleichen Namen für jede Set-, Get- und Delete-Funktion/Methode verwenden sollten).
Nach der obigen Definition ist der ursprüngliche Code für das Setzen, Abrufen und Löschen derselbe:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
Dies sollten Sie vermeiden:
def set_property(property,value):
def get_property(property):
Erstens funktioniert die obige Methode nicht, weil Sie kein Argument für die Instanz angeben, auf die die Eigenschaft gesetzt werden soll (normalerweise self
), die da wären:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
Zweitens wird dadurch der Zweck von zwei speziellen Methoden dupliziert, __setattr__
y __getattr__
.
Drittens: Wir haben auch die setattr
y getattr
eingebauten Funktionen.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
El @property
Dekorator dient der Erstellung von Gettern und Settern.
Wir könnten zum Beispiel das Einstellungsverhalten ändern, um den eingestellten Wert einzuschränken:
class Protective(object):
@property
def protected_value(self):
return self._protected_value
@protected_value.setter
def protected_value(self, value):
if acceptable(value): # e.g. type or range check
self._protected_value = value
Im Allgemeinen wollen wir die Verwendung von property
und nur direkte Attribute verwenden.
Das ist es, was die Benutzer von Python erwarten. Nach der Regel der geringsten Überraschung sollten Sie versuchen, Ihren Benutzern das zu geben, was sie erwarten, es sei denn, Sie haben einen sehr zwingenden Grund für das Gegenteil.
Demonstration
Angenommen, das geschützte Attribut unseres Objekts soll eine ganze Zahl zwischen 0 und 100 sein und sein Löschen verhindern, mit entsprechenden Meldungen, die den Benutzer über die richtige Verwendung informieren:
class Protective(object):
"""protected property demo"""
#
def __init__(self, start_protected_value=0):
self.protected_value = start_protected_value
#
@property
def protected_value(self):
return self._protected_value
#
@protected_value.setter
def protected_value(self, value):
if value != int(value):
raise TypeError("protected_value must be an integer")
if 0 <= value <= 100:
self._protected_value = int(value)
else:
raise ValueError("protected_value must be " +
"between 0 and 100 inclusive")
#
@protected_value.deleter
def protected_value(self):
raise AttributeError("do not delete, protected_value can be set to 0")
(Beachten Sie, dass __init__
bezieht sich auf self.protected_value
aber die Eigenschaftsmethoden beziehen sich auf self._protected_value
. Dies ist so, dass __init__
verwendet die Eigenschaft über die öffentliche API und stellt sicher, dass sie "geschützt" ist).
Und Verwendung:
>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0
Sind die Namen wichtig?
Ja, sie tun . .setter
y .deleter
Kopien des ursprünglichen Eigentums anzufertigen. Dadurch können Unterklassen das Verhalten ordnungsgemäß ändern, ohne das Verhalten der übergeordneten Klasse zu verändern.
class Obj:
"""property demo"""
#
@property
def get_only(self):
return self._attribute
#
@get_only.setter
def get_or_set(self, value):
self._attribute = value
#
@get_or_set.deleter
def get_set_or_delete(self):
del self._attribute
Damit dies funktioniert, müssen Sie die entsprechenden Namen verwenden:
obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error
Ich bin nicht sicher, wo dies nützlich wäre, aber der Anwendungsfall ist, wenn Sie eine Get-, Set- und/oder Delete-Only-Eigenschaft wünschen. Wahrscheinlich am besten, um semantisch gleiche Eigenschaft mit dem gleichen Namen zu halten.
Schlussfolgerung
Beginnen Sie mit einfachen Attributen.
Wenn Sie später Funktionalität rund um das Setzen, Abrufen und Löschen benötigen, können Sie diese mit dem Eigenschaftsdekorator hinzufügen.
Vermeiden Sie Funktionen mit dem Namen set_...
y get_...
- dafür sind Immobilien da.