web-dev-qa-db-fra.com

Quelle est la façon dont Pythonic utilise les accesseurs et les setters?

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.

285
Jorge Guberte

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
629
Grissiom

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.

Manifestation

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

Les noms comptent-ils?

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.

Conclusion

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.

198
Aaron Hall
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
25
Autoplectic

Découvrez le @property décorateur .

18
Kevin Little

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
12
Farzad Vertigo

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.

1
Tomasz Bartkowiak