Je voudrais comprendre comment fonctionne la fonction intégrée property
. Ce qui me trouble, c'est que property
peut également être utilisé en tant que décorateur, mais il ne prend que des arguments lorsqu'il est utilisé en tant que fonction intégrée et non en tant que décorateur.
Cet exemple provient de documentation :
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
Les arguments de property
sont getx
, setx
, delx
et une chaîne de documentation.
Dans le code ci-dessous, property
est utilisé comme décorateur. L'objet en est la fonction x
, mais dans le code ci-dessus, il n'y a pas de place pour une fonction d'objet dans les arguments.
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
Et comment les décorateurs x.setter
et x.deleter
sont-ils créés? Je suis confus.
La fonction property()
renvoie un spécial objet descripteur :
>>> property()
<property object at 0x10ff07940>
C'est cet objet qui a des méthodes extra :
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
Ceux-ci agissent en tant que décorateurs aussi . Ils renvoient un nouvel objet de propriété:
>>> property().getter(None)
<property object at 0x10ff079f0>
c'est une copie de l'ancien objet, mais avec l'une des fonctions remplacées.
Rappelez-vous que la syntaxe @decorator
n'est qu'un sucre syntaxique; la syntaxe:
@property
def foo(self): return self._foo
signifie vraiment la même chose que
def foo(self): return self._foo
foo = property(foo)
donc foo
la fonction est remplacée par property(foo)
, ce que nous avons vu ci-dessus est un objet spécial. Ensuite, lorsque vous utilisez @foo.setter()
, vous appelez la méthode property().setter
que je vous ai vue ci-dessus, qui renvoie une nouvelle copie de la propriété, mais cette fois avec la fonction setter remplacée par la méthode décorée.
La séquence suivante crée également une propriété complète, en utilisant ces méthodes de décorateur.
Nous créons d’abord quelques fonctions et un objet property
avec juste un getter:
>>> def getter(self): print 'Get!'
...
>>> def setter(self, value): print 'Set to {!r}!'.format(value)
...
>>> def deleter(self): print 'Delete!'
...
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
Ensuite, nous utilisons la méthode .setter()
pour ajouter un configurateur:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Enfin, nous ajoutons un deleter avec la méthode .deleter()
:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Dernier point mais non le moindre, l’objet property
agit comme un objet descripteur , donc il a .__get__()
, .__set__()
et .__delete__()
méthodes à connecter à un attribut d'instance pour obtenir, définir et supprimer:
>>> class Foo(object): pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
Le Descriptor Howto inclut un exemple de pure python implémentation du type property()
:
class Property(object): "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
La documentation indique qu’il s’agit d’un raccourci pour la création de propriétés en lecture seule. Alors
@property
def x(self):
return self._x
est équivalent à
def getx(self):
return self._x
x = property(getx)
La première partie est simple:
@property
def x(self): ...
est le même que
def x(self): ...
x = property(x)
property
avec seulement un getter.La prochaine étape consisterait à étendre cette propriété avec un séparateur et un séparateur. Et cela se produit avec les méthodes appropriées:
@x.setter
def x(self, value): ...
retourne une nouvelle propriété qui hérite de tout de l'ancienne x
et du séparateur donné.
x.deleter
fonctionne de la même manière.
Voici un exemple minimal de la façon dont @property
peut être implémenté:
class Thing:
def __init__(self, my_Word):
self._Word = my_Word
@property
def Word(self):
return self._Word
>>> print( Thing('ok').Word )
'ok'
Sinon, Word
reste une méthode au lieu d'une propriété.
class Thing:
def __init__(self, my_Word):
self._Word = my_Word
def Word(self):
return self._Word
>>> print( Thing('ok').Word() )
'ok'
Ce qui suit:
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
Est le même que:
class C(object):
def __init__(self):
self._x = None
def _x_get(self):
return self._x
def _x_set(self, value):
self._x = value
def _x_del(self):
del self._x
x = property(_x_get, _x_set, _x_del,
"I'm the 'x' property.")
Est le même que:
class C(object):
def __init__(self):
self._x = None
def _x_get(self):
return self._x
def _x_set(self, value):
self._x = value
def _x_del(self):
del self._x
x = property(_x_get, doc="I'm the 'x' property.")
x = x.setter(_x_set)
x = x.deleter(_x_del)
Est le même que:
class C(object):
def __init__(self):
self._x = None
def _x_get(self):
return self._x
x = property(_x_get, doc="I'm the 'x' property.")
def _x_set(self, value):
self._x = value
x = x.setter(_x_set)
def _x_del(self):
del self._x
x = x.deleter(_x_del)
Quel est le même que:
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
Voici un autre exemple sur la façon dont @property
peut aider quand on doit refactoriser le code extrait de ici (je ne le résume que ci-dessous):
Imaginez que vous avez créé une classe Money
comme ceci:
class Money:
def __init__(self, dollars, cents):
self.dollars = dollars
self.cents = cents
et un utilisateur crée une bibliothèque en fonction de cette classe où il utilise, par exemple.
money = Money(27, 12)
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.
Supposons maintenant que vous décidiez de changer votre classe Money
et de vous débarrasser des attributs dollars
et cents
, mais que vous décidiez plutôt de ne suivre que le montant total des cents:
class Money:
def __init__(self, dollars, cents):
self.total_cents = dollars * 100 + cents
Si l'utilisateur mentionné ci-dessus essaie maintenant de faire fonctionner sa bibliothèque comme auparavant
money = Money(27, 12)
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
il en résultera une erreur
AttributeError: l'objet 'Money' n'a pas d'attribut 'dollars'
Cela signifie que désormais, toute personne qui s'appuie sur votre classe Money
d'origine devrait modifier toutes les lignes de code où dollars
et cents
sont utilisés, ce qui peut être très pénible ... Alors, comment pourrait-on éviter cela? En utilisant @property
!
Voilà comment:
class Money:
def __init__(self, dollars, cents):
self.total_cents = dollars * 100 + cents
# Getter and setter for dollars...
@property
def dollars(self):
return self.total_cents // 100
@dollars.setter
def dollars(self, new_dollars):
self.total_cents = 100 * new_dollars + self.cents
# And the getter and setter for cents.
@property
def cents(self):
return self.total_cents % 100
@cents.setter
def cents(self, new_cents):
self.total_cents = 100 * self.dollars + new_cents
quand nous appelons maintenant de notre bibliothèque
money = Money(27, 12)
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.
cela fonctionnera comme prévu et nous n'avons pas eu à changer une seule ligne de code dans notre bibliothèque! En fait, nous n'aurions même pas besoin de savoir que la bibliothèque dont nous dépendons a changé.
De plus, la setter
fonctionne bien:
money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.
money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.
J'ai lu tous les articles ici et me suis rendu compte que nous aurions peut-être besoin d'un exemple concret, Pourquoi, en fait, nous avons @property? Alors, considérons une application Flask dans laquelle vous utilisez un système d'authentification. Vous déclarez un utilisateur modèle en models.py
:
class User(UserMixin, db.Model):
__table= 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
...
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
Dans ce code, nous avons l'attribut "caché" password
en utilisant @property
qui déclenche une assertion AttributeError
lorsque vous essayez d'y accéder directement, tandis que nous utilisions @ property.setter pour définir la variable d'instance réelle password_hash
.
Maintenant, dans auth/views.py
, nous pouvons instancier un utilisateur avec:
...
@auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
user = User(email=form.email.data,
username=form.username.data,
password=form.password.data)
db.session.add(user)
db.session.commit()
...
Remarquez l'attribut password
qui provient d'un formulaire d'inscription lorsqu'un utilisateur remplit le formulaire. La confirmation du mot de passe se produit au début avec EqualTo('password', message='Passwords must match')
(au cas où vous vous le demanderiez, mais les formulaires Flask associés à un sujet différent).
J'espère que cet exemple sera utile
Commençons par les décorateurs Python.
Un décorateur Python est une fonction qui permet d’ajouter des fonctionnalités supplémentaires à une fonction déjà définie.
En Python, tout est objet, En Python, tout est objet. Les fonctions en Python sont des objets de première classe, ce qui signifie qu’elles peuvent être référencées par une variable, ajoutées aux listes, passées en tant qu’arguments à une autre fonction, etc.
Considérez l'extrait de code suivant.
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
def say_bye():
print("bye!!")
say_bye = decorator_func(say_bye)
say_bye()
# Output:
# Wrapper function started
# bye
# Given function decorated
Ici, nous pouvons dire que la fonction décoratrice a modifié notre fonction say_hello et y a ajouté des lignes de code supplémentaires.
Syntaxe Python pour décorateur
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
@decorator_func
def say_bye():
print("bye!!")
say_bye()
Terminons tout par rapport à un scénario, mais avant cela, parlons de quelques principes géniaux.
Les getters et les setters sont utilisés dans de nombreux langages de programmation orientés objet pour garantir le principe de l'encapsulation de données (est considéré comme le regroupement de données avec les méthodes qui fonctionnent sur ces données).
Ces méthodes sont bien sûr le getter pour récupérer les données et le configurateur pour changer les données.
Selon ce principe, les attributs d'une classe sont privés pour les cacher et les protéger d'un autre code.
Yup, @property est fondamentalement une façon pythonique d'utiliser des accesseurs et des setters.
Python a un grand concept appelé propriété qui simplifie beaucoup la vie d'un programmeur orienté objet.
Supposons que vous décidiez de créer une classe pouvant stocker la température en degrés Celsius.
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_Fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
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
Code refactorisé, voici comment nous aurions pu le réaliser avec la propriété.
En Python, propriété () est une fonction intégrée qui crée et retourne un objet de propriété.
Un objet de propriété a trois méthodes, getter (), setter () et delete ().
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)
Ici,
temperature = property(get_temperature,set_temperature)
aurait pu être décomposé comme,
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Point à noter:
Vous pouvez maintenant accéder à la valeur de la température en écrivant.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Nous pouvons continuer et ne pas définir les noms get_temperature _ et set_température car ils sont inutiles et polluer l'espace de noms de la classe.
La manière Pythonic de traiter le problème ci-dessus consiste à utiliser @property.
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_Fahrenheit(self):
return (self.temperature * 1.8) + 32
@property
def temperature(self):
print("Getting value")
return self.temperature
@temperature.setter
def temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self.temperature = value
Points à noter -
Comme vous pouvez le constater, le code est nettement moins élégant.
Parlons maintenant d’un scénario pratique dans la vie réelle.
Disons que vous avez conçu une classe comme suit:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
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.
Et un jour fatidique, un client de confiance est venu nous voir et a suggéré que "x" devait être une valeur comprise entre 0 et 1000, c'est vraiment un scénario horrible!
En raison des propriétés, rien de plus simple: nous créons une version de propriété de "x".
class OurClass:
def __init__(self,x):
self.x = x
@property
def x(self):
return self.__x
@x.setter
def x(self, x):
if x < 0:
self.__x = 0
Elif x > 1000:
self.__x = 1000
else:
self.__x = x
C’est génial, n’est-ce pas? Vous pouvez commencer par la mise en oeuvre la plus simple qui soit, et vous êtes libre de migrer ultérieurement vers une version de propriété sans avoir à changer d’interface! Les propriétés ne remplacent donc pas les accesseurs et les passeurs!
Vous pouvez vérifier cette implémentation ici
Ce point a été éclairci par de nombreuses personnes qui se trouvaient là-haut, mais voici un point direct sur lequel j’étais en train de chercher .
class UtilityMixin():
@property
def get_config(self):
return "This is property"
L'appel de la fonction "get_config ()" fonctionnera comme ceci.
util = UtilityMixin()
print(util.get_config)
Si vous remarquez que je n’ai pas utilisé de crochets "()" pour appeler la fonction. C’est la chose fondamentale que je cherchais pour le décorateur @property. Pour que vous puissiez utiliser votre fonction comme une variable.
property
est une classe derrière @property
décorateur.
Vous pouvez toujours vérifier ceci:
print(property) #<class 'property'>
J'ai réécrit l'exemple de help(property)
pour montrer que la syntaxe @property
class C:
def __init__(self):
self._x=None
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
c = C()
c.x="a"
print(c.x)
est fonctionnellement identique à la syntaxe property()
:
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, v):
self._x = v
def d(self):
del self._x
prop = property(g,s,d)
c = C()
c.x="a"
print(c.x)
Comme vous pouvez le constater, l'utilisation de la propriété est la même.
@property
est implémenté via la classe property
.La question est donc d’expliquer un peu la classe property
. Cette ligne:
prop = property(g,s,d)
Était l'initialisation. Nous pouvons le réécrire comme ceci:
prop = property(fget=g,fset=s,fdel=d)
La signification de fget
, fset
et fdel
:
| fget
| function to be used for getting an attribute value
| fset
| function to be used for setting an attribute value
| fdel
| function to be used for del'ing an attribute
| doc
| docstring
L'image suivante montre les triplets que nous avons, de la classe property
:
__get__
, __set__
et __delete__
peuvent être remplacés par substitués . C'est l'implémentation du motif de descripteur en Python.
En général, un descripteur est un attribut d'objet avec "comportement de liaison", un accès dont l'attribut a été remplacé par des méthodes du protocole de descripteur.
Nous pouvons également utiliser les méthodes property setter
, getter
et deleter
pour lier la fonction à la propriété. Vérifiez l'exemple suivant. La méthode s2
de la classe C
définira la propriété doubled .
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, x):
self._x = x
def d(self):
del self._x
def s2(self,x):
self._x=x+x
x=property(g)
x=x.setter(s)
x=x.deleter(d)
c = C()
c.x="a"
print(c.x) # outputs "a"
C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"
Une propriété peut être déclarée de deux manières.
Vous pouvez consulter quelques exemples que j'ai écrits à propos de properties dans python .
Voici un autre exemple:
##
## Python Properties Example
##
class GetterSetterExample( object ):
## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
__x = None
##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
self.x = 1234
return None
##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
_value = ( self.__x, _default )[ self.__x == None ]
## Debugging - so you can see the order the calls are made...
print( '[ Test Class ] Get x = ' + str( _value ) )
## Return the value - we are a getter afterall...
return _value
##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
## Debugging - so you can see the order the calls are made...
print( '[ Test Class ] Set x = ' + str( _value ) )
## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
if ( _value > 0 ):
self.__x = -_value
else:
self.__x = _value
##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
## Unload the assignment / data for x
if ( self.__x != None ):
del self.__x
##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
## Output the x property data...
print( '[ x ] ' + str( self.x ) )
## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
return '\n'
##
##
##
_test = GetterSetterExample( )
print( _test )
## For some reason the deleter isn't being called...
del _test.x
Fondamentalement, le même que l’exemple C (objet), sauf que j’utilise plutôt x ... Je n’initialise pas non plus dans __init - ... eh bien, oui, mais il peut être supprimé car __x est défini comme faisant partie de la classe ....
La sortie est:
[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234
et si je commente le self.x = 1234 dans init , alors le résultat est:
[ Test Class ] Get x = None
[ x ] None
et si je règle _default = None sur _default = 0 dans la fonction getter (tous les getters devraient avoir une valeur par défaut mais elle n'est pas transmise par les valeurs de propriété de ce que j'ai vu afin que vous puissiez la définir ici, et ce n'est pas grave car vous pouvez définir la valeur par défaut une fois et l'utiliser partout), à savoir: def x (self, _default = 0):
[ Test Class ] Get x = 0
[ x ] 0
Remarque: La logique de lecture est juste là pour que la valeur soit manipulée par elle afin de s'assurer qu'elle est manipulée par elle - la même chose pour les instructions d'impression ...
Remarque: je suis habitué à Lua et je suis capable de créer dynamiquement plus de 10 aides lorsque j'appelle une fonction unique et que j'ai créé quelque chose de similaire pour Python sans utiliser de propriétés et que cela fonctionne dans une certaine mesure, mais même si les fonctions ont été créées auparavant. étant utilisé, il y a toujours des problèmes avec le fait qu'ils soient appelés avant d'être créés, ce qui est étrange car ce n'est pas codé de cette façon ... Je préfère la flexibilité des méta-tables Lua et le fait que je peux utiliser de réels setters/getters Au lieu d’accéder directement à une variable, j’aime bien la rapidité avec laquelle certaines choses peuvent être construites avec Python, par exemple les programmes graphiques. Bien que celui que je conçois ne soit pas possible sans beaucoup de bibliothèques supplémentaires - si je le code dans AutoHotkey, je peux accéder directement aux appels dll dont j'ai besoin, et la même chose peut être faite en Java, C #, C++, etc. - peut-être que je Je n'ai pas encore trouvé le bon choix, mais je peux passer de Python à ce projet ..
Note: Le code dans ce forum est cassé - je devais ajouter des espaces dans la première partie du code pour que cela fonctionne - lors du copier/coller, assurez-vous de convertir tous les espaces en onglets .... J'utilise des onglets pour Python car dans un fichier de 10 000 lignes d'une taille pouvant aller de 512 Ko à 1 Mo avec des espaces et de 100 à 200 Ko avec des onglets, ce qui équivaut à une différence énorme en termes de taille de fichier et de réduction du temps de traitement ...
Les onglets peuvent également être ajustés par utilisateur. Ainsi, si vous préférez 2 espaces de largeur, 4, 8 ou tout ce que vous pouvez faire, cela signifie qu’il est judicieux pour les développeurs déficients visuels.
Remarque: Toutes les fonctions définies dans la classe ne sont pas correctement mises en retrait à cause d'un bogue dans le logiciel de forum. Assurez-vous de l'indenter si vous copiez/collez
Une remarque: Pour moi, pour Python 2.x, @property n'a pas fonctionné comme annoncé quand je n'ai pas hérité de l'objet formulaire:
class A():
pass
mais a travaillé quand:
class A(object):
pass
pour Pyhton 3, a toujours travaillé