Commençons par un peu d'histoire, parce que l'origine de la mise en œuvre a été équivalent à celui de votre alternative (équivalent, car property
est implémenté en C dans Disponible de sorte que le getter
, etc. sont écrits en C pas clair Python").
Cependant il a été rapporté que la question (1620) sur le Python bug tracker en 2007:
Tel que rapporté par Duncan Stand à
http://permalink.gmane.org/gmane.comp.python.general/551183 la nouvelle
@spam.getter syntaxe modifie la propriété en place, mais il doit créer
un nouveau.
Le patch est la première ébauche d'une solution. J'ai à écrire des tests unitaires pour
vérifier le patch. Il copie la propriété et en bonus, attrape le
__doc__
chaîne de caractères à partir de la lecture si la doc de la chaîne est d'abord venu de la
de lecture ainsi.
Malheureusement, le lien ne doit pas aller n'importe où (je ne sais vraiment pas pourquoi on appelle ça un "permalink" ...). Il a été classé comme un bug et changé à la forme actuelle (voir ce patch ou le Github commit (mais c'est une combinaison de plusieurs patchs)). Dans le cas où vous ne voulez pas suivre le lien le changement:
PyObject *
property_getter(PyObject *self, PyObject *getter)
{
- Py_XDECREF(((propertyobject *)self)->prop_get);
- if (getter == Py_None)
- getter = NULL;
- Py_XINCREF(getter);
- ((propertyobject *)self)->prop_get = getter;
- Py_INCREF(self);
- return self;
+ return property_copy(self, getter, NULL, NULL, NULL);
}
Et pour setter
et deleter
. Si vous ne savez pas C les lignes importantes sont:
((propertyobject *)self)->prop_get = getter;
et
return self;
le reste est principalement "Python C API standard". Cependant, ces deux lignes sont équivalentes à votre:
self.fget = fget
return self
Et il a été changé pour:
return property_copy(self, getter, NULL, NULL, NULL);
qui, essentiellement, n':
return type(self)(fget, self.fset, self.fdel, self.__doc__)
Pourquoi était-il changé?
Puisque le lien est en bas je ne sais pas la raison exacte, mais je peux faire des suppositions basées sur les cas de test dans ce commit:
import unittest
class PropertyBase(Exception):
pass
class PropertyGet(PropertyBase):
pass
class PropertySet(PropertyBase):
pass
class PropertyDel(PropertyBase):
pass
class BaseClass(object):
def __init__(self):
self._spam = 5
@property
def spam(self):
"""BaseClass.getter"""
return self._spam
@spam.setter
def spam(self, value):
self._spam = value
@spam.deleter
def spam(self):
del self._spam
class SubClass(BaseClass):
@BaseClass.spam.getter
def spam(self):
"""SubClass.getter"""
raise PropertyGet(self._spam)
@spam.setter
def spam(self, value):
raise PropertySet(self._spam)
@spam.deleter
def spam(self):
raise PropertyDel(self._spam)
class PropertyTests(unittest.TestCase):
def test_property_decorator_baseclass(self):
# see #1620
base = BaseClass()
self.assertEqual(base.spam, 5)
self.assertEqual(base._spam, 5)
base.spam = 10
self.assertEqual(base.spam, 10)
self.assertEqual(base._spam, 10)
delattr(base, "spam")
self.assert_(not hasattr(base, "spam"))
self.assert_(not hasattr(base, "_spam"))
base.spam = 20
self.assertEqual(base.spam, 20)
self.assertEqual(base._spam, 20)
self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
def test_property_decorator_subclass(self):
# see #1620
sub = SubClass()
self.assertRaises(PropertyGet, getattr, sub, "spam")
self.assertRaises(PropertySet, setattr, sub, "spam", None)
self.assertRaises(PropertyDel, delattr, sub, "spam")
self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
C'est similaire aux exemples les autres réponses déjà fournies. Le problème, c'est que vous voulez être en mesure de modifier le comportement dans une sous-classe, sans affecter la classe parent:
>>> b = BaseClass()
>>> b.spam
5
Cependant, avec votre propriété, il en résulterait ceci:
>>> b = BaseClass()
>>> b.spam
---------------------------------------------------------------------------
PropertyGet Traceback (most recent call last)
PropertyGet: 5
Cela se produit parce que BaseClass.spam.getter
(qui est utilisé en SubClass
) modifie et renvoie l' BaseClass.spam
de la propriété!
Donc, oui, il avait été changé (très probable), car elle permet de modifier le comportement d'une propriété dans une sous-classe sans changer le comportement de la classe parent.
Une autre raison (?)
Notez qu'il y a une raison supplémentaire, ce qui est un peu idiot, mais en fait vaut la peine de mentionner (à mon avis):
Récapitulons brièvement: Un décorateur est juste sucre syntaxique pour une mission, donc:
@decorator
def decoratee():
pass
est équivalent à:
def func():
pass
decoratee = decorator(func)
del func
Le point important ici est que le résultat de la décoratrice est attribué le nom de l'décoré de la fonction. Ainsi, alors que vous utilisez le plus souvent le même "nom de fonction" pour les getter/setter/deleter - vous n'avez pas à!
Par exemple:
class Fun(object):
@property
def a(self):
return self._a
@a.setter
def b(self, value):
self._a = value
>>> o = Fun()
>>> o.b = 100
>>> o.a
100
>>> o.b
100
>>> o.a = 100
AttributeError: can't set attribute
Dans cet exemple, vous utilisez le descripteur de fichier pour a
créer un autre descripteur pour b
qui se comporte comme a
, sauf qu'il a un setter
.
C'est un peu bizarre d'exemple et n'est probablement pas utilisé très souvent (tous les). Mais même si c'est plutôt étrange et (pour moi) pas très bon style, doivent illustrer que juste parce que vous utilisez property_name.setter
(ou getter
/deleter
) qu'il doit être lié à l' property_name
. Il pourrait être lié à un nom! Et je ne m'attends pas à se propager à l'origine de la propriété (bien que je ne suis pas vraiment sûr de ce que je m'attends à voir ici).
Résumé
- Disponible en fait utilisé la fonction "modifier et renvoyer
self
" approche en getter
, setter
et deleter
une fois.
- Il a été modifié à cause d'un rapport de bug.
- Il s'est comporté "buggy" lorsqu'il est utilisé avec une sous-classe qui a remplacé une propriété de la classe parent.
- Plus généralement: les Décorateurs ne peut pas l'influence de ce nom, ils seront liés donc l'hypothèse que c'est toujours valable pour
return self
dans un décorateur peut être contestable (pour un usage général, décorateur).