Exemple du monde réel sur la façon d'utiliser la fonctionnalité de propriété en python?

143

Je suis intéressé par la façon de l'utiliser @propertyen Python. J'ai lu la documentation python et l'exemple là-bas, à mon avis, n'est qu'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 (s) avantage (s) je peux tirer d'emballer le _xrempli avec le décorateur. Pourquoi ne pas simplement mettre en œuvre comme:

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

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

Merci.

xiao 啸
la source
10
C'est la meilleure et la plus propre explication que j'ai trouvée sur le décorateur de propriété [cliquez ici ]
Sumudu
2
@Anubis dans le dernier exemple du lien que vous avez fourni, le réglage c = Celsius (-500) n'a généré aucune ValueError, ce qui, je pense, n'atteint pas le résultat escompté.
Sajuuk
D'accord avec @Anubis. Il est correctement implémenté ici: python-course.eu/python3_properties.php
anon01

Réponses:

92

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

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
benosteen
la source
1
J'aime l'exemple PDB_Calculator - les choses compliquées sont abstraites, tout fonctionne et l'utilisateur peut profiter de la simplicité!
Adam Kurkiewicz
2
probablement, du point de vue des professionnels, ce sont de très bons exemples. Mais, en tant que noobie, je trouve ces exemples assez inefficaces. mon mauvais ... :(
kmonsoor
78

Un cas d'utilisation simple sera de définir un attribut d'instance en lecture seule, comme vous le savez, le fait de diriger un nom de variable avec un trait _xde soulignement en python signifie généralement qu'il est privé (usage interne), mais parfois nous voulons être en mesure de lire l'attribut d'instance et non d'écrire afin que nous puissions l'utiliser propertypour cela:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
mouad
la source
7
On peut encore définir c._x, si l'utilisateur le souhaite. Python n'a en fait pas de vrais attributs privés.
21

Jetez un œil à cet article pour une utilisation très pratique. En bref, il explique comment en Python, vous pouvez généralement abandonner la méthode getter / setter explicite, car si vous en avez besoin à un moment donné, vous pouvez l'utiliser propertypour une implémentation transparente.

Eli Bendersky
la source
15

Une chose pour laquelle je l'ai utilisé est la mise en cache des valeurs lentes à rechercher, mais inchangées, stockées dans une base de données. Cela se généralise à toute situation où vos attributs nécessitent un calcul ou une autre opération longue (par exemple, vérification de la 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 pouvait ne nécessiter qu'un seul attribut particulier de ce type, mais les objets sous-jacents eux-mêmes pouvaient avoir plusieurs de ces attributs - les initialiser tous lors de la construction serait un gaspillage, et les propriétés me permettent d'être flexible dans lequel les attributs sont paresseux et qui ne le sont pas.

detly
la source
1
Tu ne peux pas utiliser @cached_propertypour ça?
adarsh
@adarsh ​​- Cela semble intéressant. Où est-ce?
detly
Je l'ai utilisé mais j'ai oublié que ce n'était pas intégré, mais vous pouvez l'utiliser avec ceci, pypi.python.org/pypi/cached-property/0.1.5
adarsh
2
Intéressant. Je pense qu'il a été publié pour la première fois après cette réponse, mais quiconque lisant ceci devrait probablement l'utiliser à la place.
detly
10

En lisant les réponses et les commentaires, le thème principal semble être les réponses semblent manquer d'un exemple simple mais utile. J'en ai inclus un très simple ici qui démontre l'utilisation simple du @propertydécorateur. 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, c'est in_feet-à- dire 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
Mike
la source
pour moi personnellement, les meilleurs exemples de getters / setters sont de montrer aux gens le genre de changements que vous devez faire plus tard, mais évidemment, cela prend un peu plus de temps.
dtc
7

La propriété est juste une abstraction autour d'un champ qui vous donne plus de contrôle sur la manière dont un champ spécifique peut être manipulé et pour effectuer des calculs middleware. Peu des usages 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
Stanislav Ageev
la source
6

Oui, pour l'exemple d'origine publié, la propriété fonctionnera exactement de la même manière qu'une simple variable d'instance «x».

C'est la meilleure chose à propos des propriétés python. De l'extérieur, elles fonctionnent exactement comme des variables d'instance! Ce qui vous permet d'utiliser des variables d'instance en dehors de la classe.

Cela signifie que votre premier exemple pourrait en fait utiliser une variable d'instance. Si les choses ont changé et que vous décidez de modifier votre implémentation et qu'une propriété est utile, l'interface avec la propriété serait toujours la même à partir du code en dehors de la classe. Un changement de variable d'instance à propriété n'a aucun impact sur le code en dehors de la classe.

De nombreux autres langages et cours de programmation indiqueront qu'un programmeur ne doit jamais exposer de variables d'instance, et utiliser à la place des `` getters '' et des `` setters '' pour toute valeur accessible depuis l'extérieur de la classe, même le cas simple cité dans la question.

Code en dehors de la classe avec de nombreux langages (par exemple Java) utilisent

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

Et lors de l'implémentation de la classe, il y a de nombreux 'getters' et 'setters' qui font exactement comme votre premier exemple: répliquer simplement une variable d'instance. Ces getters et setters sont nécessaires car si l'implémentation de la classe change, tout le code en dehors de la classe devra changer. Mais les propriétés python permettent au code en dehors de la classe d'être le même que celui des variables d'instance. Ainsi, le code en dehors de la classe n'a pas besoin d'être modifié si vous ajoutez une propriété ou si vous avez une variable d'instance simple. Donc, contrairement à la plupart des langages orientés objet, pour votre exemple simple, vous pouvez utiliser la variable d'instance au lieu des `` getters '' et des `` setters '' qui ne sont vraiment pas nécessaires, sachant que si vous passez à une propriété à l'avenir, le code utilisant votre classe n'a pas besoin de changer.

Cela signifie que vous n'avez besoin de créer des propriétés que s'il y a un comportement complexe, et pour le cas simple très courant où, comme décrit dans la question, une simple variable d'instance est tout ce qui est nécessaire, vous pouvez simplement utiliser la variable d'instance.

innov8
la source
6

une autre fonctionnalité intéressante des propriétés sur l'utilisation des setters et des getters est qu'ils vous permettent de continuer à utiliser OP = opérateurs (par exemple + =, - =, * = etc) sur vos attributs tout en conservant toute validation, contrôle d'accès, mise en cache, etc. les setters et les getters fourniraient.

par exemple, si vous avez écrit la classe Personavec 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 ageune propriété, vous pourriez écrire le plus propre:

bob.age += 1
quizdog
la source
5

La réponse courte à votre question 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.

La raison pour laquelle les propriétés existent, c'est que si votre code change dans le futur, et que vous devez soudainement faire plus avec vos données: mettre en cache les valeurs, protéger l'accès, interroger une ressource externe ... peu importe, vous pouvez facilement modifier votre classe pour ajouter des getters et setters pour les données sans changer l'interface, vous n'avez donc pas à trouver partout dans votre code où ces données sont accessibles et à changer cela aussi.

CuillèreMeiser
la source
4

Ce que beaucoup ne remarquent pas au début, c'est que vous pouvez créer vos propres sous-classes de propriétés. J'ai trouvé cela très utile pour exposer des attributs d'objets en lecture seule ou des attributs que vous pouvez lire et écrire mais pas supprimer. C'est également un excellent moyen d'encapsuler des fonctionnalités telles que le suivi des modifications des 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')

la source
J'aime ça. Est-ce que je lis ceci correctement dans ce lecteur est en lecture seule tandis que l'accesseur est en lecture / écriture sans capacité de suppression? Comment ajouteriez-vous la validation des données? Je suis assez nouveau dans Python mais je pense qu'il existe probablement un moyen d'ajouter un rappel à la attr = reader('_attr')ligne ou une forme de pré-vérification comme attr = if self.__isValid(value): reader('_attr'). Suggestions?
Gabe Spradlin
Désolé, je viens de réaliser que je demandais la validation des données pour une variable en lecture seule. Mais évidemment, cela ne s'appliquerait qu'à la partie setter de la classe accesseur. Alors changez attr = reader('_attr')pour attr = accessor('_attr'). Merci
Gabe Spradlin
Vous avez raison de dire que si vous vouliez une validation, vous ajouteriez une fonction pour valider et lever l'exception si elle est invalide (ou quel que soit le comportement que vous avez aimé, y compris ne rien faire) à l' init . J'ai modifié ce qui précède avec un modèle possible. Le validateur doit renvoyer True | False pour indiquer si l'ensemble se produit ou non.