Préférez les propriétés . C'est pour ça qu'ils sont là.
La raison en est que tous les attributs sont publics en Python. Le début des noms avec un ou deux traits de soulignement n'est qu'un avertissement que l'attribut donné est un détail d'implémentation qui pourrait ne pas rester le même dans les futures versions du code. Cela ne vous empêche pas d'obtenir ou de définir cet attribut. Par conséquent, l'accès aux attributs standard est la façon normale et pythonique d'accéder aux attributs.
L'avantage des propriétés est qu'elles sont syntaxiquement identiques à l'accès aux attributs, vous pouvez donc passer de l'une à l'autre sans modifier le code client. Vous pouvez même avoir une version d'une classe qui utilise des propriétés (par exemple, pour le code par contrat ou le débogage) et une autre qui ne l'est pas pour la production, sans changer le code qui l'utilise. Dans le même temps, vous n'avez pas besoin d'écrire des getters et setters pour tout, au cas où vous pourriez avoir besoin de mieux contrôler l'accès plus tard.
En Python, vous n'utilisez pas de getters, de setters ou de propriétés juste pour le plaisir. Vous utilisez d'abord des attributs, puis plus tard, uniquement si nécessaire, migrez éventuellement vers une propriété sans avoir à modifier le code à l'aide de vos classes.
Il y a en effet beaucoup de code avec l'extension .py qui utilise des getters et setters et l'héritage et des classes inutiles partout où par exemple un simple tuple ferait l'affaire, mais c'est du code de personnes écrivant en C ++ ou Java en utilisant Python.
Ce n'est pas du code Python.
la source
L'utilisation des propriétés vous permet de commencer avec des accès d'attributs normaux, puis de les sauvegarder avec des getters et des setters par la suite si nécessaire .
la source
La réponse courte est: les propriétés gagnent haut la main . Toujours.
Il y a parfois un besoin de getters et de setters, mais même alors, je les "cache" au monde extérieur. Il y a beaucoup de façons de le faire en Python (
getattr
,setattr
,__getattribute__
, etc ..., mais très concise et propre est:Voici un bref article qui présente le sujet des getters et setters en Python.
la source
property
. (Il ressemble à une affectation simple, mais il appelle une fonction.) Par conséquent, "Pythonic" est essentiellement un terme dénué de sens, sauf par la définition tautologique: "Les conventions Pythonic sont des choses que nous avons définies comme étant Pythonic."property
fonctionnalité menace de rendre l'idée des axiomes pythoniques presque sans valeur. Il ne nous reste donc qu'une liste de contrôle.self
objet , des paramètres explicites peuvent être utiles. Par exemple,user.email = "..."
il ne semble pas qu'il puisse déclencher une exception car il ressemble à la définition d'un attribut, alorsuser.set_email("...")
qu'il est clair qu'il pourrait y avoir des effets secondaires comme des exceptions.[ TL; DR? Vous pouvez passer à la fin pour un exemple de code .]
En fait, je préfère utiliser un idiome différent, qui est un peu impliqué pour une utilisation unique, mais c'est bien si vous avez un cas d'utilisation plus complexe.
Un peu de fond d'abord.
Les propriétés sont utiles en ce qu'elles nous permettent de gérer à la fois la définition et l'obtention de valeurs de manière programmatique, mais permettent toujours d'accéder aux attributs en tant qu'attributs. Nous pouvons transformer «obtient» en «calculs» (essentiellement) et nous pouvons transformer «ensembles» en «événements». Disons donc que nous avons la classe suivante, que j'ai codée avec des getters et setters de type Java.
Vous vous demandez peut-être pourquoi je n'ai pas appelé
defaultX
etdefaultY
dans la__init__
méthode de l'objet . La raison en est que dans notre cas, je veux supposer que lessomeDefaultComputation
méthodes renvoient des valeurs qui varient dans le temps, par exemple un horodatage, et chaque fois quex
(ouy
) n'est pas défini (où, dans le cadre de cet exemple, «non défini» signifie «défini à Aucun ") Je veux la valeur du calcul par défaut dex
(ouy
de).C'est donc boiteux pour un certain nombre de raisons décrites ci-dessus. Je vais le réécrire en utilisant les propriétés:
Qu'avons-nous gagné? Nous avons acquis la possibilité de faire référence à ces attributs en tant qu'attributs même si, en coulisse, nous finissons par exécuter des méthodes.
Bien sûr, le vrai pouvoir des propriétés est que nous voulons généralement que ces méthodes fassent quelque chose en plus de simplement obtenir et définir des valeurs (sinon cela ne sert à rien d'utiliser des propriétés). Je l'ai fait dans mon exemple getter. Nous exécutons essentiellement un corps de fonction pour récupérer une valeur par défaut chaque fois que la valeur n'est pas définie. Il s'agit d'un schéma très courant.
Mais que perdons-nous et que ne pouvons-nous pas faire?
Le principal inconvénient, à mon avis, est que si vous définissez un getter (comme nous le faisons ici), vous devez également définir un setter. [1] C'est un bruit supplémentaire qui encombre le code.
Un autre inconvénient est que nous devons encore initialiser les valeurs
x
et . (Eh bien, bien sûr, nous pourrions les ajouter en utilisant, mais c'est plus de code supplémentaire.)y
__init__
setattr()
Troisièmement, contrairement à l'exemple de type Java, les getters ne peuvent pas accepter d'autres paramètres. Maintenant, je vous entends déjà dire, eh bien, si cela prend des paramètres, ce n'est pas un getter! D'un point de vue officiel, c'est vrai. Mais dans un sens pratique, il n'y a aucune raison de ne pas pouvoir paramétrer un attribut nommé - comme
x
- et définir sa valeur pour certains paramètres spécifiques.Ce serait bien si nous pouvions faire quelque chose comme:
par exemple. Le plus proche que nous pouvons obtenir est de remplacer l'affectation pour impliquer une sémantique spéciale:
et bien sûr, assurez-vous que notre setter sait comment extraire les trois premières valeurs en tant que clé d'un dictionnaire et définir sa valeur sur un nombre ou quelque chose.
Mais même si nous le faisions, nous ne pourrions toujours pas le supporter avec des propriétés car il n'y a aucun moyen d'obtenir la valeur car nous ne pouvons pas du tout passer de paramètres au getter. Nous avons donc dû tout retourner, introduisant une asymétrie.
Le getter / setter de style Java nous permet de gérer cela, mais nous sommes de retour à avoir besoin de getter / setters.
Dans mon esprit, ce que nous voulons vraiment, c'est quelque chose qui reflète les exigences suivantes:
Les utilisateurs définissent une seule méthode pour un attribut donné et peuvent y indiquer si l'attribut est en lecture seule ou en lecture-écriture. Les propriétés échouent à ce test si l'attribut est accessible en écriture.
Il n'est pas nécessaire que l'utilisateur définisse une variable supplémentaire sous-jacente à la fonction, nous n'avons donc pas besoin de
__init__
ousetattr
dans le code. La variable existe simplement du fait que nous avons créé cet attribut de nouveau style.Tout code par défaut pour l'attribut s'exécute dans le corps de la méthode lui-même.
Nous pouvons définir l'attribut comme attribut et le référencer comme attribut.
Nous pouvons paramétrer l'attribut.
En termes de code, nous voulons un moyen d'écrire:
et pouvoir ensuite faire:
et ainsi de suite.
Nous voulons également un moyen de le faire pour le cas spécial d'un attribut paramétrable, mais nous permettons toujours au cas d'affectation par défaut de fonctionner. Vous verrez comment j'ai abordé cela ci-dessous.
Maintenant au point (yay! Le point!). La solution que j'ai trouvée pour cela est la suivante.
Nous créons un nouvel objet pour remplacer la notion de propriété. L'objet est destiné à stocker la valeur d'une variable qui lui est définie, mais conserve également une poignée sur le code qui sait calculer une valeur par défaut. Son travail consiste à stocker l'ensemble
value
ou à exécuter lemethod
si cette valeur n'est pas définie.Appelons ça un
UberProperty
.Je suppose que
method
c'est une méthode de classe,value
c'est la valeur de laUberProperty
, et j'ai ajoutéisSet
parce queNone
peut être une vraie valeur et cela nous permet une façon propre de déclarer qu'il n'y a vraiment "aucune valeur". Une autre façon est une sorte de sentinelle.Cela nous donne essentiellement un objet qui peut faire ce que nous voulons, mais comment pouvons-nous le mettre sur notre classe? Eh bien, les propriétés utilisent des décorateurs; pourquoi pas nous? Voyons à quoi cela pourrait ressembler (à partir de maintenant, je vais m'en tenir à l'utilisation d'un seul «attribut»,
x
).Bien sûr, cela ne fonctionne pas encore. Nous devons implémenter
uberProperty
et nous assurer qu'il gère à la fois les get et les sets.Commençons par get.
Ma première tentative a été de simplement créer un nouvel objet UberProperty et de le renvoyer:
J'ai rapidement découvert, bien sûr, que cela ne fonctionne pas: Python ne lie jamais l'appelable à l'objet et j'ai besoin de l'objet pour appeler la fonction. Même la création du décorateur dans la classe ne fonctionne pas, car bien que nous ayons maintenant la classe, nous n'avons toujours pas d'objet avec lequel travailler.
Nous allons donc devoir pouvoir faire plus ici. Nous savons qu'une méthode n'a besoin d'être représentée qu'une seule fois, alors allons-y et gardons notre décorateur, mais modifions
UberProperty
pour ne stocker que lamethod
référence:Ce n'est pas non plus appelable, donc pour le moment rien ne fonctionne.
Comment complétons-nous l'image? Eh bien, que finissons-nous quand nous créons l'exemple de classe en utilisant notre nouveau décorateur:
dans les deux cas, nous récupérons ce
UberProperty
qui bien sûr n'est pas appelable, donc cela n'est pas très utile.Ce dont nous avons besoin, c'est d'un moyen de lier dynamiquement l'
UberProperty
instance créée par le décorateur après la création de la classe à un objet de la classe avant que cet objet ne soit renvoyé à cet utilisateur pour utilisation. Ouais, c'est un__init__
appel, mec.Écrivons ce que nous voulons que notre résultat de recherche soit le premier. Nous lions un
UberProperty
à une instance, donc une chose évidente à retourner serait un BoundUberProperty. C'est là que nous conserverons l'état de l'x
attribut.Maintenant, nous la représentation; comment les intégrer à un objet? Il existe quelques approches, mais la plus simple à expliquer utilise simplement la
__init__
méthode pour effectuer ce mappage. Au moment où__init__
nous appelons nos décorateurs ont couru, il suffit donc de regarder à travers l'objet__dict__
et de mettre à jour tous les attributs où la valeur de l'attribut est de typeUberProperty
.Maintenant, les propriétés uber sont cool et nous voudrons probablement les utiliser beaucoup, il est donc logique de simplement créer une classe de base qui le fait pour toutes les sous-classes. Je pense que vous savez comment la classe de base sera appelée.
Nous ajoutons ceci, changeons notre exemple pour hériter de
UberObject
, et ...Après avoir modifié
x
pour être:Nous pouvons exécuter un test simple:
Et nous obtenons la sortie que nous voulions:
(Gee, je travaille tard.)
Notez que je l' ai utilisé
getValue
,setValue
etclearValue
ici. C'est parce que je n'ai pas encore lié les moyens de les renvoyer automatiquement.Mais je pense que c'est un bon endroit pour s'arrêter pour l'instant, car je suis fatigué. Vous pouvez également voir que la fonctionnalité de base que nous voulions est en place; le reste est de la vitrine. Habillage de fenêtre de convivialité important, mais cela peut attendre jusqu'à ce que j'ai un changement pour mettre à jour le message.
Je terminerai l'exemple dans la prochaine publication en abordant ces choses:
Nous devons nous assurer que UberObject
__init__
est toujours appelé par des sous-classes.Nous devons nous assurer que nous gérons le cas commun où quelqu'un «alias» une fonction pour autre chose, comme:
Nous devons
e.x
revenire.x.getValue()
par défaut.e.x.getValue()
. (Faire cela est évident, si vous ne l'avez pas déjà résolu.)Nous devons soutenir la configuration
e.x directly
, comme danse.x = <newvalue>
. Nous pouvons également le faire dans la classe parente, mais nous devrons mettre à jour notre__init__
code pour le gérer.Enfin, nous ajouterons des attributs paramétrés. La façon dont nous procéderons également devrait être assez évidente.
Voici le code tel qu'il existe jusqu'à présent:
[1] Je suis peut-être en retard sur la question de savoir si c'est toujours le cas.
la source
return self.x or self.defaultX()
c'est un code dangereux. Que se passe-t-il quandself.x == 0
?__getitem__
méthode. Ce serait bizarre cependant, car vous auriez alors un python complètement non standard.Je pense que les deux ont leur place. Un problème avec l'utilisation
@property
est qu'il est difficile d'étendre le comportement des getters ou setters dans les sous-classes en utilisant des mécanismes de classe standard. Le problème est que les fonctions getter / setter réelles sont cachées dans la propriété.Vous pouvez réellement mettre la main sur les fonctions, par exemple avec
vous pouvez accéder aux fonctions getter et setter au fur
C.p.fget
et à mesureC.p.fset
, mais vous ne pouvez pas facilement utiliser les fonctions d'héritage de méthode normales (par exemple super) pour les étendre. Après avoir creusé dans les subtilités du super, vous pouvez en effet utiliser le super de cette manière:L'utilisation de super () est cependant assez maladroite, car la propriété doit être redéfinie, et vous devez utiliser le mécanisme super (cls, cls) légèrement contre-intuitif pour obtenir une copie non liée de p.
la source
L'utilisation des propriétés est pour moi plus intuitive et s'intègre mieux dans la plupart des codes.
Comparant
contre.
est pour moi assez évident qui est plus facile à lire. Les propriétés permettent également de rendre les variables privées beaucoup plus faciles.
la source
Je préférerais ne pas utiliser ni l'un ni l'autre dans la plupart des cas. Le problème avec les propriétés est qu'elles rendent la classe moins transparente. Surtout, c'est un problème si vous leviez une exception à partir d'un setter. Par exemple, si vous avez une propriété Account.email:
alors l'utilisateur de la classe ne s'attend pas à ce que l'attribution d'une valeur à la propriété puisse provoquer une exception:
Par conséquent, l'exception peut ne pas être gérée et se propager trop haut dans la chaîne d'appels pour être traitée correctement, ou entraîner une traceback très inutile qui est présentée à l'utilisateur du programme (ce qui est malheureusement trop courant dans le monde de python et java). ).
Je voudrais également éviter d'utiliser des getters et setters:
Au lieu de propriétés et de getters / setters, je préfère faire la logique complexe dans des endroits bien définis, comme dans une méthode de validation:
ou une méthode Account.save similaire.
Notez que je n'essaie pas de dire qu'il n'y a pas de cas où les propriétés sont utiles, mais seulement que vous pourriez être mieux si vous pouvez rendre vos classes assez simples et transparentes pour que vous n'en ayez pas besoin.
la source
validate()
méthode dans une classe. Une propriété est simplement utilisée lorsque vous avez une logique complexe derrière uneobj.x = y
affectation simple , et cela dépend de la nature de la logique.J'ai l'impression que les propriétés consistent à vous permettre d'obtenir les frais généraux d'écriture des getters et setters uniquement lorsque vous en avez réellement besoin.
La culture de programmation Java conseille fortement de ne jamais donner accès aux propriétés, et de passer par des getters et setters, et uniquement ceux qui sont réellement nécessaires. Il est un peu bavard de toujours écrire ces morceaux de code évidents, et notez que 70% du temps ils ne sont jamais remplacés par une logique non triviale.
En Python, les gens se soucient réellement de ce type de surcharge, de sorte que vous pouvez adopter la pratique suivante:
@property
pour les implémenter sans changer la syntaxe du reste de votre code.la source
Je suis surpris que personne n'ait mentionné que les propriétés sont des méthodes liées d'une classe de descripteurs, Adam Donohue et NeilenMarais ont exactement cette idée dans leurs messages - que les getters et setters sont des fonctions et peuvent être utilisés pour:
Cela présente une puce moyen de masquer les détails de l'implémentation et le code brut comme l'expression régulière, les transtypages, essayez .. sauf les blocs, les assertions ou les valeurs calculées.
En général, faire CRUD sur un objet peut souvent être assez banal, mais considérons l'exemple des données qui seront conservées dans une base de données relationnelle. Les ORM peuvent masquer les détails d'implémentation de vernaculaires SQL particuliers dans les méthodes liées à fget, fset, fdel définies dans une classe de propriétés qui gérera les échelles if .. elif .. else si laides dans le code OO - exposant le simple et élégant
self.variable = something
et évite les détails pour le développeur utilisant l'ORM.Si l'on ne considère les propriétés que comme un vestige morne d'un langage de Bondage et de Discipline (c'est-à-dire Java), il leur manque le point de descripteurs.
la source
Dans les projets complexes, je préfère utiliser des propriétés en lecture seule (ou des getters) avec une fonction de définition explicite:
Dans les projets de longue durée, le débogage et la refactorisation prennent plus de temps que l'écriture du code lui-même. Il y a plusieurs inconvénients à utiliser
@property.setter
qui rendent le débogage encore plus difficile:1) python permet de créer de nouveaux attributs pour un objet existant. Il est donc très difficile de suivre une erreur d'impression suivante:
Si votre objet est un algorithme compliqué, vous passerez un certain temps à essayer de comprendre pourquoi il ne converge pas (remarquez un «t» supplémentaire dans la ligne ci-dessus)
2) setter peut parfois évoluer vers une méthode compliquée et lente (par exemple, toucher une base de données). Il serait assez difficile pour un autre développeur de comprendre pourquoi la fonction suivante est très lente. Il pourrait consacrer beaucoup de temps à la
do_something()
méthode de profilage , alors qu'ilmy_object.my_attr = 4.
est en fait la cause du ralentissement:la source
Les
@property
getters et setters traditionnels ont tous deux leurs avantages. Cela dépend de votre cas d'utilisation.Les avantages de
@property
Vous n'avez pas besoin de changer l'interface tout en modifiant l'implémentation de l'accès aux données. Lorsque votre projet est petit, vous souhaiterez probablement utiliser un accès direct aux attributs pour accéder à un membre de la classe. Par exemple, supposons que vous ayez un objet
foo
de typeFoo
, qui a un membrenum
. Ensuite, vous pouvez simplement obtenir ce membre avecnum = foo.num
. Au fur et à mesure que votre projet se développe, vous pouvez avoir l'impression qu'il doit y avoir des vérifications ou des débogages sur l'accès aux attributs simples. Ensuite, vous pouvez le faire avec un@property
intérieur la classe. L'interface d'accès aux données reste la même, de sorte qu'il n'est pas nécessaire de modifier le code client.Cité de PEP-8 :
L'utilisation
@property
pour l'accès aux données en Python est considérée comme Pythonic :Il peut renforcer votre auto-identification en tant que programmeur Python (pas Java).
Cela peut aider votre entretien d'embauche si votre enquêteur pense que les getters et setters de style Java sont anti-modèles .
Avantages des getters et setters traditionnels
Les getters et setters traditionnels permettent un accès aux données plus compliqué qu'un simple accès aux attributs. Par exemple, lorsque vous définissez un membre de classe, vous avez parfois besoin d'un indicateur indiquant où vous souhaitez forcer cette opération même si quelque chose ne semble pas parfait. Bien qu'il ne soit pas évident de savoir comment augmenter un accès direct à un membre
foo.num = num
, vous pouvez facilement augmenter votre setter traditionnel avec unforce
paramètre supplémentaire :Les getters et setters traditionnels expliquent clairement que l'accès d'un membre de classe se fait via une méthode. Ça signifie:
Ce que vous obtenez comme résultat peut ne pas être identique à ce qui est exactement stocké dans cette classe.
Même si l'accès ressemble à un simple accès aux attributs, les performances peuvent varier considérablement.
À moins que les utilisateurs de votre classe n'attendent un
@property
cacher derrière chaque déclaration d'accès aux attributs, rendre ces choses explicites peut aider à minimiser les surprises de vos utilisateurs de classe.Comme mentionné par @NeilenMarais et dans cet article , l'extension des getters et setters traditionnels dans les sous-classes est plus facile que l'extension des propriétés.
Les getters et setters traditionnels sont largement utilisés depuis longtemps dans différentes langues. Si vous avez des gens d'horizons différents dans votre équipe, ils semblent plus familiers que
@property
. De plus, à mesure que votre projet se développe, si vous devez migrer de Python vers une autre langue qui n'en a pas@property
, l'utilisation de getters et setters traditionnels rendrait la migration plus fluide.Avertissements
Ni
@property
les getters ni les setters traditionnels ne rendent le membre de la classe privé, même si vous utilisez un double soulignement avant son nom:la source
Voici un extrait de "Python efficace: 90 façons spécifiques d'écrire mieux en Python" (livre incroyable. Je le recommande vivement).
la source