with - python property object




Exemple réel sur la façon d'utiliser la propriété en python? (8)

D'autres exemples seraient la validation / le filtrage des attributs de l'ensemble (les forçant à être dans les limites ou acceptables) et l'évaluation paresseuse de termes complexes ou en évolution rapide.

Calcul complexe caché derrière un attribut:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validation:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

Je suis intéressé par l'utilisation de @property en Python. J'ai lu les docs python et l'exemple ici, à mon avis, est juste un code jouet:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Je ne sais pas quel avantage (s) je peux obtenir de l'emballage du _x rempli avec le décorateur de propriété. Pourquoi ne pas simplement mettre en œuvre comme:

class C(object):
    def __init__(self):
        self.x = None

Je pense que la fonctionnalité de propriété peut être utile dans certaines situations. Mais quand? Quelqu'un pourrait-il me donner des exemples concrets?

Merci.


En parcourant les réponses et les commentaires, le thème principal semble être les réponses qui semblent manquer un exemple simple mais utile. J'en ai inclus un très simple ici qui démontre l'utilisation simple du décorateur @property . C'est une classe qui permet à un utilisateur de spécifier et d'obtenir une mesure de distance en utilisant une variété d'unités différentes, à savoir in_feet ou in_metres .

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Usage:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

La propriété est juste une abstraction autour d'un champ qui vous donne plus de contrôle sur la façon dont un champ spécifique peut être manipulé et faire des calculs de middleware. Peu d'utilisations qui me viennent à l'esprit sont la validation et l'initialisation préalable et la restriction d'accès

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

La réponse courte à votre question, c'est que dans votre exemple, il n'y a aucun avantage. Vous devriez probablement utiliser le formulaire qui n'implique pas de propriétés.

Les propriétés de la raison existent, c'est que si votre code change dans le futur, et vous avez soudainement besoin d'en faire plus avec vos données: cachez les valeurs, protégez l'accès, interrogez une ressource externe ... peu importe, vous pouvez facilement modifier votre classe pour ajouter des getters et les paramètres pour les données sans changer l'interface, de sorte que vous n'avez pas à trouver partout dans votre code où ces données sont accessibles et changer cela aussi.


Python a un grand concept appelé propriété qui rend la vie d'un programmeur orienté objet beaucoup plus simple.

Avant de définir et d'entrer dans les détails de ce qu'est @property, construisons d'abord une intuition sur les raisons pour lesquelles il serait nécessaire en premier lieu.

Un exemple pour commencer Supposons que vous décidiez de créer une classe capable de stocker la température en degrés Celsius. Il serait également mettre en œuvre une méthode pour convertir la température en degrés Fahrenheit. Une façon de le faire est la suivante.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

Nous pourrions faire des objets hors de cette classe et manipuler la température de l'attribut comme nous le souhaitions. Essayez-les sur le shell Python.

>>> # create new object
>>> man = Celsius()

>>> # set temperature
>>> man.temperature = 37

>>> # get temperature
>>> man.temperature
37

>>> # get degrees Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001

Les décimales supplémentaires lors de la conversion en Fahrenheit sont dues à l'erreur arithmétique en virgule flottante (essayez 1.1 + 2.2 dans l'interpréteur Python).

Chaque fois que nous assignons ou récupérons un attribut d'objet comme la température, comme montré ci-dessus, Python le recherche dans le dictionnaire de dictée de l'objet.

>>> man.__dict__
{'temperature': 37}

Par conséquent, la température interne devient l'homme. dict ['température'].

Supposons maintenant que notre classe est devenue populaire parmi les clients et qu'ils ont commencé à l'utiliser dans leurs programmes. Ils ont fait toutes sortes d'affectations à l'objet.

Un jour fatidique, un client de confiance est venu à nous et a suggéré que les températures ne peuvent pas descendre en dessous de -273 degrés Celsius (les étudiants en thermodynamique pourraient argumenter qu'il est en fait -273.15), aussi appelé le zéro absolu. Il nous a également demandé d'implémenter cette contrainte de valeur. Étant une entreprise qui vise la satisfaction du client, nous avons répondu avec plaisir à la suggestion et avons publié la version 1.01 (une mise à niveau de notre classe existante).

Utilisation de Getters et de Setters Une solution évidente à la contrainte ci-dessus sera de cacher l'attribut temperature (rendre privé) et de définir de nouvelles interfaces getter et setter pour le manipuler. Cela peut être fait comme suit.

class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)
    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32
    # new update
    def get_temperature(self):
        return self._temperature
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value

Nous pouvons voir ci-dessus que les nouvelles méthodes get_temperature () et set_temperature () ont été définies et de plus, la température a été remplacée par la température. Un trait de soulignement ( ) au début est utilisé pour désigner les variables privées dans Python.

>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)

>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

Cette mise à jour a implémenté avec succès la nouvelle restriction. Nous ne sommes plus autorisés à régler la température en-dessous de -273.

Veuillez noter que les variables privées n'existent pas en Python. Il y a simplement des normes à suivre. La langue elle-même n'applique aucune restriction.

>>> c._temperature = -300
>>> c.get_temperature()
-300

Mais ce n'est pas très préoccupant. Le gros problème avec la mise à jour ci-dessus est que tous les clients qui ont implémenté notre classe précédente dans leur programme doivent modifier leur code de obj.temperature à obj.get_temperature () et toutes les affectations comme obj.temperature = val à obj.set_temperature ( val).

Ce refactoring peut causer des maux de tête aux clients avec des centaines de milliers de lignes de codes.

Dans l'ensemble, notre nouvelle mise à jour n'était pas rétrocompatible. C'est là que la propriété vient à la rescousse.

La puissance de @property La façon pythonique de traiter le problème ci-dessus est d'utiliser la propriété. Voici comment nous aurions pu l'atteindre.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    def get_temperature(self):
        print("Getting value")
        return self._temperature
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value
    temperature = property(get_temperature,set_temperature)

Et, lancez le code suivant dans le shell une fois que vous l'exécutez.

>>> c = Celsius()

Nous avons ajouté une fonction print () dans get_temperature () et set_temperature () pour clairement observer qu'ils sont en cours d'exécution.

La dernière ligne du code, fait une température d'objet de propriété. Autrement dit, la propriété attache du code (get_temperature et set_temperature) aux accès aux attributs membres (température).

Tout code qui récupère la valeur de la température appellera automatiquement get_temperature () au lieu d'une recherche de dictionnaire ( dict ). De même, tout code attribuant une valeur à la température appellera automatiquement set_temperature (). C'est une fonctionnalité intéressante en Python.

La méthode de propriété devrait contenir getter puis le setter. Pas l'inverse, Python appellera la première méthode (get_temperature) comme getter et la seconde méthode (set_temperature) comme setter.

temperature = property(get_temperature,set_temperature)

Possibles récurseurs possibles

Dans la méthode setter, si nous n'utilisions pas Underscore avant l'attribut d'objet comme ci-dessous, cela donnerait un appel récursif de setter et RecursionError se produirait.

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

Quelque chose que beaucoup ne remarquent pas au début, c'est que vous pouvez créer vos propres sous-classes de propriété. J'ai trouvé cela très utile pour exposer des attributs d'objets en lecture seule ou un attribut que vous pouvez lire et écrire mais pas supprimer. C'est également un excellent moyen d'intégrer des fonctionnalités telles que le suivi des modifications apportées aux champs d'objets.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Une autre caractéristique intéressante des propriétés est d'utiliser les opérateurs OP = (p.ex. + =, - =, * = etc) sur vos attributs tout en conservant la validation, le contrôle d'accès, la mise en cache, etc. les setters et getters fourniraient.

Par exemple, si vous avez écrit la classe Person avec un setter setage(newage) et un getter getage() , alors pour incrémenter l'âge, vous devrez écrire:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

mais si vous faites de l' age une propriété, vous pouvez écrire le plus propre:

bob.age += 1

Une chose que j'ai utilisée pour mettre en cache des valeurs lentes à rechercher, mais immuables, stockées dans une base de données. Cela généralise à toute situation où vos attributs nécessitent un calcul ou une autre opération longue (par exemple, vérification de base de données, communication réseau) que vous ne voulez faire qu'à la demande.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

C'était dans une application Web où une vue de page donnée ne nécessitait qu'un attribut particulier de ce type, mais les objets sous-jacents eux-mêmes pouvaient avoir plusieurs de ces attributs - les initialiser en construction serait gaspilleur et les propriétés me permettent d'être flexibles. les attributs sont paresseux et ceux qui ne le sont pas.







python-decorators