web-dev-qa-db-fra.com

Quand et comment utiliser la fonction intégrée property () en python

Il me semble qu’à part un peu de sucre syntaxique, property () ne fait rien de bon.

Bien sûr, il est agréable de pouvoir écrire a.b=2 au lieu de a.setB(2), mais cacher le fait que ab = 2 n'est pas une simple affectation ressemble à une recette de problème, soit parce qu'un résultat inattendu peut survenir, tel que a.b=2 provoque réellement a.b à être 1. Ou une exception est levée. Ou un problème de performance. Ou juste être déroutant.

Pouvez-vous me donner un exemple concret pour un bon usage? (l'utiliser pour corriger le code problématique ne compte pas ;-)

67
olamundo

Dans les langages qui reposent sur des accesseurs et des setters, comme Java, ils ne sont supposés ni censés faire quoi que ce soit - ce serait étonnant si x.getB() faisait autre chose que renvoyer la valeur actuelle de l'attribut logique b ou si x.setB(2) le faisait n'importe quoi mais peu importe le peu de travail interne qui est nécessaire pour que x.getB() renvoie 2.

Cependant, il n'y a pas de garanties langues imposées par le comportement attendu, c'est-à-dire des contraintes imposées par le compilateur sur le corps des méthodes dont le nom commence par get ou set: c'est plutôt du sens commun, une convention sociale. , "guides de style" et tests.

Le comportement des accès x.b et des assignations telles que x.b = 2, dans les langages qui possèdent des propriétés (un ensemble de langages incluant, sans toutefois s'y limiter, Python) est exactement identique à celui des méthodes getter et setter, par exemple, Java: les mêmes attentes, le même manque de garanties imposées par le langage.

La première victoire pour les propriétés est la syntaxe et la lisibilité. Avoir à écrire, par exemple,

x.setB(x.getB() + 1)

au lieu de l'évidence

x.b += 1

crie pour se venger des dieux. Dans les langages qui prennent en charge les propriétés, il n'y a absolument aucune bonne raison pour forcer les utilisateurs de la classe à suivre les girations d'un tel passe-partout byzantin, ce qui a un impact sur la lisibilité de leur code, sans aucun avantage.

En Python en particulier, l’utilisation de propriétés (ou d’autres descripteurs) au lieu de getters et de setters présente un autre avantage: si et quand vous réorganisez votre classe de manière à ce que le setter et le getter sous-jacents ne soient plus nécessaires, vous pouvez API publiée) éliminent simplement ces méthodes et la propriété qui les utilise, faisant de b un attribut "stocké" normal de la classe de x plutôt que "logique" obtenu et défini par des calculs.

En Python, le fait d’agir directement (lorsque cela est faisable) au moyen de méthodes est une optimisation importante, et l’utilisation systématique de propriétés permet de réaliser cette optimisation chaque fois que cela est possible (exposant toujours directement les "attributs stockés normaux", et uniquement ceux nécessitant un calcul lors de l’accès). et/ou paramétrage via méthodes et propriétés).

Ainsi, si vous utilisez des accesseurs et des régulateurs au lieu de propriétés, outre le fait de nuire à la lisibilité du code de vos utilisateurs, vous gaspillez aussi gratuitement les cycles de la machine (et l'énergie fournie à leur ordinateur pendant ces cycles ;-) , encore sans raison valable.

Votre seul argument contre les propriétés est par exemple. qu '"un utilisateur externe ne s'attendrait généralement à aucun effet secondaire résultant d'une affectation"; mais vous manquez le fait que le même utilisateur (dans un langage tel que Java où les accesseurs et les setters sont omniprésents) ne s'attendrait pas à des "effets secondaires" (observables) suite à l'appel d'un passeur, que ce soit (et encore moins pour un getter) ;-) Ce sont des attentes raisonnables et c'est à vous, en tant qu'auteur de classe, d'essayer de vous adapter - que votre installateur et votre acquéreur soient utilisés directement ou via une propriété, ne fait aucune différence. Si vous avez des méthodes avec des effets secondaires observables importants, ne pas appelez-les getThis, setThat et ne les utilisez pas via les propriétés.

La plainte selon laquelle les propriétés "cachent l'implémentation" est totalement injustifiée: la plupart tous de OOP concerne l'implémentation du masquage d'informations - confier à une classe la responsabilité de présenter une interface logique au monde extérieur et de la mettre en œuvre. en interne du mieux qu'il peut. Les accesseurs et les setters, exactement comme les propriétés, sont des outils pour atteindre cet objectif. Les propriétés font simplement un meilleur travail (dans les langues qui les supportent ;-).

125
Alex Martelli

L'idée est de vous permettre d'éviter d'avoir à écrire des accesseurs et des setters avant d'en avoir réellement besoin.

Donc, pour commencer, vous écrivez:

class MyClass(object):
    def __init__(self):
        self.myval = 4

Évidemment, vous pouvez maintenant écrire myobj.myval = 5.

Mais plus tard, vous décidez que vous avez besoin d'un passeur, car vous voulez faire quelque chose d'intelligent en même temps. Mais vous ne voulez pas avoir à changer tout le code qui utilise votre classe - vous enveloppez donc le séparateur dans le décorateur @property, et tout cela fonctionne.

31
Daniel Roseman

mais cacher le fait que a.b = 2 n'est pas un une affectation simple ressemble à une recette pour des ennuis

Vous ne cachez pas ce fait cependant; ce fait n'a jamais été là pour commencer. Ceci est python - un langage de haut niveau; pas l'Assemblée. Quelques-unes des déclarations "simples" qu'il contient se résument à une seule instruction du processeur. Lire la simplicité dans une tâche, c'est lire des choses qui n'y sont pas.

Quand vous dites x.b = c, tout ce que vous devriez penser est probablement que "quoi que cela se passe, x.b devrait maintenant être c".

14
Lee B

Une raison de base est vraiment simplement que ça a l'air mieux. C'est plus Pythonic. Surtout pour les bibliothèques. quelque chose.getValue () a l'air moins gentil que quelque chose.value

Dans plone (un assez gros CMS), vous utilisiez autrefois document.setTitle () qui stocke beaucoup de choses, comme stocker la valeur, l’indexer à nouveau, etc. Il suffit de faire document.title = 'quelque chose' est plus agréable. Vous savez de toute façon que beaucoup de choses se passent dans les coulisses.

5
Reinout van Rees

Vous avez raison, ce n'est que du sucre syntaxique. Il se peut qu’il n’y ait pas de bonne utilisation en fonction de votre définition du code problématique.

Considérez que vous avez une classe Foo qui est largement utilisée dans votre application. Maintenant, cette application est devenue assez volumineuse et laisse supposer que c'est une application Web qui est devenue très populaire.

Vous identifiez que Foo est à l'origine d'un goulot d'étranglement. Peut-être est-il possible d'ajouter un peu de cache à Foo pour l'accélérer. L'utilisation de propriétés vous permettra de le faire sans modifier le code ou les tests en dehors de Foo.

Oui, bien sûr, c'est un code problématique, mais vous venez d'économiser beaucoup d'argent en le réparant rapidement.

Et si Foo est dans une bibliothèque pour laquelle vous avez des centaines ou des milliers d'utilisateurs? Eh bien, vous vous êtes évité de devoir leur demander de faire un refactor coûteux lors de la mise à niveau vers la dernière version de Foo.

Les notes de publication contiennent un élément relatif à Foo au lieu d’un guide de portage de paragraphe.

Les programmeurs Python expérimentés n'attendent pas grand chose de a.b=2 autre que a.b==2, mais ils savent même que cela peut ne pas être vrai. Ce qui se passe à l'intérieur de la classe, c'est sa propre affaire.

3
John La Rooy

Voici un vieil exemple de la mienne. J'ai enveloppé une bibliothèque C qui avait des fonctions telles que "void dt_setcharge (int atom_handle, int new_charge)" et "int dt_getcharge (int atom_handle)". Je voulais au niveau Python faire "atom.charge = atom.charge + 1".

Le décorateur "propriété" facilite les choses. Quelque chose comme:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

Il y a 10 ans, lorsque j'ai écrit ce paquet, j'ai dû utiliser __getattr__ et __setattr__, ce qui a rendu cela possible, mais l'implémentation était beaucoup plus sujette aux erreurs.

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value
2
Andrew Dalke

les getters et les setters sont nécessaires à de nombreuses fins et sont très utiles car ils sont transparents pour le code. Ayant objet Quelque chose est la hauteur de la propriété, vous affectez une valeur à Something.height = 10, mais si height a un getter et un setter, vous pouvez effectuer plusieurs opérations dans les procédures, comme la validation d'une valeur min valeur, comme déclencher un événement parce que la hauteur a changé, définissant automatiquement d'autres valeurs en fonction de la nouvelle valeur de hauteur, tout ce qui peut se produire au moment où la valeur Something.height a été affectée. N'oubliez pas que vous n'avez pas besoin de les appeler dans votre code, ils sont automatiquement exécutés au moment où vous lisez ou écrivez la valeur de la propriété. D'une certaine manière, elles ressemblent aux procédures événementielles lorsque la propriété X change de valeur et lorsque la valeur de la propriété X est lue.

0
Avenida Gez