Je le fais comme:
def set_property(property,value):
def get_property(property):
ou
object.property = value
value = object.property
Je connais Python pour la première fois, je suis donc toujours en train d’explorer la syntaxe et je voudrais quelques conseils pour le faire.
Essayez ceci: Propriété Python
Le code exemple est:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
print("getter of x called")
return self._x
@x.setter
def x(self, value):
print("setter of x called")
self._x = value
@x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
Quelle est la façon dont Pythonic utilise les accesseurs et les setters?
La manière "Pythonic" est pas d'utiliser "getters" et "setters", mais d'utiliser des attributs simples, comme le montre la question, et del
pour le déréférencement (mais les noms sont changés en protéger l'innocent ... construit):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
Si vous souhaitez modifier le paramètre et obtenir ultérieurement, vous pouvez le faire sans modifier le code utilisateur, en utilisant le décorateur property
:
class Obj:
"""property demo"""
#
@property
def attribute(self): # implements the get - this name is *the* name
return self._attribute
#
@attribute.setter
def attribute(self, value): # name must be the same
self._attribute = value
#
@attribute.deleter
def attribute(self): # again, name must be the same
del self._attribute
(Chaque décorateur copie et met à jour l'objet de propriété précédent. Notez donc que vous devriez probablement utiliser le même nom pour chaque ensemble, obtenir et supprimer une fonction/méthode.)
Après avoir défini ce qui précède, les paramètres d'origine, d'obtention et de suppression sont les mêmes:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
Vous devriez éviter ceci:
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 sur laquelle la propriété serait définie (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 duplique le but de deux méthodes spéciales, __setattr__
et __getattr__
.
Troisièmement, nous avons également les fonctions intégrées setattr
et getattr
.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
Le décorateur @property
sert à créer des getters et des setters.
Par exemple, nous pourrions modifier le comportement du paramètre pour imposer des restrictions à 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 moins surprise, vous devriez essayer de donner à vos utilisateurs ce à quoi ils s'attendent, sauf si vous avez une raison très convaincante de l'inverse.
Par exemple, supposons que l'attribut protected de notre objet soit un entier compris entre 0 et 100, et empêche sa suppression, avec des messages appropriés pour informer l'utilisateur de sa bonne utilisation:
class Protective(object):
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")
Et 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
Oui, ils font. .setter
et .deleter
copient la propriété d'origine. Cela permet aux sous-classes de modifier correctement le comportement sans en modifier le comportement dans le 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 trop où cela pourrait être utile, mais le cas d'utilisation est le cas, si vous voulez une propriété get, set et/ou delete-only. Il est probablement préférable de s'en tenir sémantiquement à la même propriété ayant le même nom.
Commencez avec des attributs simples.
Si, par la suite, vous avez besoin de fonctionnalités relatives aux paramètres, à l'obtention et à la suppression, vous pouvez les ajouter à l'aide du décorateur de propriétés.
Évitez les fonctions nommées set_...
et get_...
- c'est à cela que servent les propriétés.
In [1]: class test(object):
def __init__(self):
self.pants = 'pants'
@property
def p(self):
return self.pants
@p.setter
def p(self, value):
self.pants = value * 2
....:
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
Découvrez le @property
décorateur .
Utiliser @property
et @attribute.setter
vous aide non seulement à utiliser la méthode "Pythonic", mais également à vérifier la validité des attributs lors de la création et de la modification de l'objet.
class Person(object):
def __init__(self, p_name=None):
self.name = p_name
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if type(new_name) == str: #type checking for name property
self._name = new_name
else:
raise Exception("Invalid value for name")
En faisant cela, vous "cachez" l'attribut _name
des développeurs du client et effectuez également des vérifications sur le type de propriété name. Notez qu'en suivant cette approche même pendant l'initiation, le configurateur est appelé. Alors:
p = Person(12)
Mènera à:
Exception: Invalid value for name
Mais:
>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Vous pouvez utiliser des accesseurs/mutateurs (c'est-à-dire @attr.setter
et @property
) ou non, mais la chose la plus importante est que soit cohérent!
PEP8 , Conception pour l'héritage dit:
Pour les attributs de données publics simples, il est préférable de n’exposer que le nom de l’attribut, sans méthodes compliquées d’accesseur/mutateur . Gardez à l'esprit que Python fournit un moyen simple d'améliorer les améliorations futures, si vous estimez qu'un attribut de données simple doit développer son comportement fonctionnel. Dans ce cas, utilisez des propriétés pour masquer l'implémentation fonctionnelle derrière la syntaxe d'accès aux attributs de données simples.
Par contre, selon le Guide de style de Google règles/propriétés du langage Python , il est recommandé de:
Utilisez les propriétés du nouveau code pour accéder ou définir des données là où vous auriez normalement utilisé des méthodes simples et simples d’accesseur ou de définition. Les propriétés doivent être créées avec le décorateur
@property
.
Les avantages de cette approche:
La lisibilité est améliorée en éliminant les appels de méthodes get et set explicites pour un accès simple à un attribut. Permet aux calculs d'être paresseux. Considéré comme le moyen pythonique de maintenir l'interface d'une classe. En termes de performances, autoriser les contournements de propriétés nécessitant des méthodes d'accesseur triviales lorsqu'un accès direct aux variables est raisonnable. Cela permet également d’ajouter des méthodes d’accesseurs à l’avenir sans casser l’interface.
et contre:
Doit hériter de
object
dans Python 2. Peut masquer les effets secondaires de la même manière que la surcharge de l'opérateur. Peut être déroutant pour les sous-classes.