Quelle est la façon pythonique d'utiliser les getters et setters?
La manière "Pythonique" n'est pas d'utiliser des "getters" et des "setters", mais d'utiliser des attributs simples, comme le montre la question, et del
pour les supprimer (mais les noms sont modifiés pour protéger les innocents ... intégrés):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
Si plus tard, vous souhaitez modifier le paramètre et obtenir, vous pouvez le faire sans avoir à modifier le code utilisateur, en utilisant le property
décorateur:
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
(Chaque décorateur utilise des copies et met à jour l'objet de propriété précédent, alors notez que vous devez utiliser le même nom pour chaque fonction / méthode définie, récupérée et supprimée.
Après avoir défini ce qui précède, le paramètre d'origine, l'obtention et la suppression du code sont les mêmes:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
Vous devez éviter cela:
def set_property(property,value):
def get_property(property):
Premièrement, ce qui précède ne fonctionne pas, car vous ne fournissez pas d'argument pour l'instance que la propriété serait définie sur (généralement self
), ce qui serait:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
Deuxièmement, cela fait double emploi avec l'objectif de deux méthodes spéciales, __setattr__
et __getattr__
.
Troisièmement, nous avons également les fonctions setattr
et getattr
intégrées.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
Le @property
décorateur sert à créer des getters et des setters.
Par exemple, nous pouvons modifier le comportement du paramètre pour placer des restrictions sur la valeur définie:
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
En général, nous voulons éviter d'utiliser property
et simplement utiliser des attributs directs.
C'est ce à quoi s'attendent les utilisateurs de Python. En suivant la règle de la moindre surprise, vous devriez essayer de donner à vos utilisateurs ce qu'ils attendent, sauf si vous avez une raison très convaincante du contraire.
Manifestation
Par exemple, supposons que nous voulions que l'attribut protégé de notre objet soit un entier compris entre 0 et 100 inclus, et empêcher sa suppression, avec des messages appropriés pour informer l'utilisateur de sa bonne utilisation:
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")
(Notez que __init__
fait référence à self.protected_value
mais les méthodes de propriété font référence self._protected_value
. C'est ainsi qui __init__
utilise la propriété via l'API publique, garantissant qu'elle est "protégée".)
Et l'utilisation:
>>> 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
Les noms importent-ils?
Oui, ils le font . .setter
et .deleter
faire des copies de la propriété d'origine. Cela permet aux sous-classes de modifier correctement le comportement sans altérer le comportement du parent.
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
Maintenant, pour que cela fonctionne, vous devez utiliser les noms respectifs:
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
Je ne sais pas où cela serait utile, mais le cas d'utilisation est si vous voulez une propriété get, set et / ou delete-only. Il vaut probablement mieux s'en tenir à la même propriété sémantiquement portant le même nom.
Conclusion
Commencez avec des attributs simples.
Si vous avez besoin ultérieurement de fonctionnalités pour définir, obtenir et supprimer, vous pouvez l'ajouter avec le décorateur de propriétés.
Évitez les fonctions nommées set_...
et get_...
- c'est à cela que servent les propriétés.