Comment fonctionne le décorateur @property?
je voudrais comprendre comment la fonction intégrée property
. Ce qui me confond, c'est que property
peut aussi être utilisé comme décorateur, mais il ne prend des arguments que lorsqu'il est utilisé comme fonction intégrée et non pas lorsqu'il est utilisé comme décorateur.
cet exemple est tiré de la 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.")
property
arguments sont getx
, setx
, delx
et un doc de la chaîne.
dans le code ci-dessous property
est utilisé comme décorateur. Son objet est la fonction x
, mais dans le code ci-dessus il n'y a pas de place pour une fonction 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 x.setter
et x.deleter
décorateurs sont-ils créés?
Je suis confus.
10 réponses
la fonction property()
renvoie un descripteur :
>>> property()
<property object at 0x10ff07940>
c'est cet objet qui a extra méthodes:
>>> 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 comme des décorateurs trop . Ils retournent un nouvel objet de propriété:
>>> property().getter(None)
<property object at 0x10ff079f0>
qui est une copie de l'ancien objet, mais avec une des fonctions remplacées.
rappelez-vous que la syntaxe @decorator
n'est que du 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)
so foo
la fonction est remplacée par property(foo)
, que nous avons vu ci-dessus est un objet spécial. Ensuite, lorsque vous utilisez @foo.setter()
, ce que vous faites est d'appeler que property().setter
méthode que je vous ai montré ci-dessus, qui renvoie une nouvelle copie de la propriété, mais cette fois avec le setter fonction 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 décorateur.
d'abord nous créons quelques fonctions et un property
objet 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 setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
dernier 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
le Dernier mais non le moindre, le property
objet agit comme un descripteur de l'objet , de sorte qu'il a .__get__()
, .__set__()
et .__delete__()
méthodes de raccordement dans l'instance de l'attribut arriver, ajout et la suppression de:
>>> class Foo(object): pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
le descripteur Howto inclut un pur Python sample implementation du property()
type:
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 dit c'est juste un raccourci pour créer des propriétés readonly. So
@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)
- qui, à son tour, est la syntaxe simplifiée pour créer un
property
avec juste un getter.
La prochaine étape serait d'étendre cette propriété avec un setter et un deleter. Et cela se produit avec les méthodes appropriées:
@x.setter
def x(self, value): ...
rend un nouveau bien qui hérite de tout de l'ancien x
plus le setter donné.
x.deleter
fonctionne de la même manière.
voici un exemple minimal de la façon dont @property
peut être mis en œuvre:
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'un bien.
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)
qui 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
j'ai lu tous les billets ici et j'ai réalisé que nous pourrions avoir besoin d'un exemple de la vie réelle, pourquoi, en fait, nous avons @propriété?
Ainsi, considérez une application flasque où vous utilisez le système d'authentification.
Vous déclarez un utilisateur modèle dans models.py
:
class User(UserMixin, db.Model):
__tablename__ = '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" caché "l'attribut password
en utilisant @property
qui déclenche l'assertion AttributeError
lorsque vous essayez d'y accéder directement, alors que nous utilisons @property.setter pour définir la variable d'instance réelle password_hash
.
Maintenant auth/views.py
nous pouvons instancier un Utilisateur:
...
@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()
...
Avis attribut password
qui vient d'un formulaire d'inscription lorsqu'un utilisateur remplit le formulaire. La confirmation du mot de passe Se produit à l'avant avec EqualTo('password', message='Passwords must match')
(dans le cas où vous vous demandez, mais c'est un sujet différent liés flasque formulaires).
j'espère que cet exemple sera utile
commençons par les décorateurs en Python.
un décorateur Python est une fonction qui aide à 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 dans les listes, passées en argument à une autre fonction, etc.
Consider 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 decorator a modifié notre fonction say_hello et y a ajouté quelques lignes supplémentaires de code.
syntaxe Python for decorator
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()
concluons tout avec un scénario de cas, mais avant cela, parlons de quelques principes oops.
Getters et setters sont utilisés dans de nombreux objets langages de programmation orientés pour assurer le principe de l'encapsulation des données(est considéré comme le groupement des données avec les méthodes qui opèrent sur ces données.)
ces méthodes sont bien sûr le getter pour extraire les données et le setter pour changer les données.
Selon ce principe, les attributs d'une classe sont privés de cacher et de protéger de l'autre code.
Ouais, @property est fondamentalement une façon pythonique d'utiliser getters et setters.
Python a un grand concept appelé propriété qui rend la vie d'un programmeur orienté objet beaucoup plus simple.
supposons que vous décidez de faire une classe qui peut stocker la température en degré 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 l'obtenir avec la propriété.
en Python, property() est une fonction intégrée qui crée et renvoie 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:
- get_temperature reste une propriété au lieu d'une méthode.
Maintenant vous pouvez accéder à la valeur de la température en écrivant.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
nous pouvons aller plus loin et ne pas définir les noms get_temperature et set_temperature car ils sont inutiles et polluent l'espace de noms de classe.
le Pythonic way pour traiter le problème ci-dessus est d'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 -
- une méthode utilisée pour obtenir une valeur est décorée de"@property".
- la méthode qui doit fonctionner comme le setter est décoré avec" @temperature.setter", Si la fonction a été appelée "x", nous aurions à le décorer avec "@x.setter".
- , Nous avons écrit "deux" méthodes avec le même nom et un nombre différent de paramètres "def température(auto)" et "température def (self, x)".
comme vous pouvez le voir, le code est certainement moins élégant.
maintenant,parlons d'un vrai scénario pratique.
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)
maintenant, supposons en outre que notre classe est devenue populaire parmi les clients et ils ont commencé à l'utiliser dans leurs programmes, ils ont fait toutes sortes de missions à l'objet.
et un jour fatidique, un client de confiance est venu nous voir et nous a suggéré que" x " devait être une valeur entre 0 et 1000, c'est vraiment un scénario horrible!
en raison de propriétés c'est facile: 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 avec la plus simple implémentation imaginable, et vous êtes libre de migrer plus tard vers une version de propriété sans avoir à changer l'interface! Si les propriétés ne sont pas seulement un remplacement pour getter et setter!
vous pouvez vérifier cette implémentation ici
une propriété peut être déclarée de deux façons.
- créer les méthodes getter, setter pour un attribut et les passer ensuite comme argument à propriété fonction
- utilisant le @propriété décorateur.
vous pouvez jeter un oeil aux quelques exemples que j'ai écrits sur les propriétés en 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
essentiellement, le même que L'exemple C( objet ) sauf que j'utilise x à la place... Je n'initialise pas non plus dans _ _ init - ... bien.. Je fais, mais il peut être retiré parce que __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 moi.x = 1234 dans init alors la sortie est:
[ Test Class ] Get x = None
[ x ] None
et si je mets _default = None à _default = 0 dans la fonction getter ( comme tous les getters devraient avoir une valeur par défaut mais elle n'est pas passée par les valeurs de propriété de ce que j'ai vu donc vous pouvez la définir ici, et elle n'est pas mauvaise parce que vous pouvez définir la valeur par défaut une fois et l'utiliser partout ) ie: Def x (self, _default = 0 ):
[ Test Class ] Get x = 0
[ x ] 0
Note: la logique getter est là juste pour que la valeur soit manipulée par elle pour s'assurer qu'elle est manipulé par elle - même pour l'impression des déclarations...
Note: je suis habitué à Lua et à pouvoir dynamiquement créer des helpers 10+ lorsque j'appelle une seule fonction et que J'ai fait quelque chose de similaire pour Python sans utiliser de propriétés et que cela fonctionne jusqu'à un certain point, mais, même si les fonctions sont créées avant d'être utilisées, il y a toujours des problèmes avec le fait qu'elles soient appelées avant d'être créées, ce qui est étrange car ce n'est pas codé de cette façon... Je préfère la flexibilité de Les méta-tables Lua et le fait que je puisse utiliser des setters / getters réels au lieu d'accéder essentiellement directement à une variable... J'aime quand même la rapidité avec laquelle certaines choses peuvent être construites avec Python - par exemple les programmes gui. bien qu'un que je suis en train de concevoir puisse ne pas être possible sans beaucoup de bibliothèques supplémentaires - si je le code en 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++, et plus - peut-être que je n'ai pas encore trouvé la bonne chose mais pour ce projet je peux changez de Python..
Note: la sortie du code dans ce forum est cassée - j'ai dû ajouter des espaces à la première partie du code pour qu'il fonctionne - quand copier / coller s'assurer que vous convertissez tous les espaces en onglets.... J'utilise des onglets pour Python parce que dans un fichier qui est de 10 000 lignes le fichier peut être 512KB à 1MB avec des espaces et 100 à 200KB avec des onglets qui équivaut à une différence massive pour la taille du fichier, et la réduction du temps de traitement...
Onglets peuvent également être ajusté par utilisateur - donc, si vous préférez 2 espaces largeur, 4, 8 ou tout ce que vous pouvez le faire, ce qui signifie qu'il est réfléchi pour les développeurs avec des déficits de la vue.
Note: toutes les fonctions définies dans la classe ne sont pas indentées correctement à cause d'un bug dans le logiciel du forum - assurez-vous de l'indenter si vous copiez / collez
ci-dessous est un autre exemple sur la façon dont @property
peut aider quand on a à modifier le code qui est pris de ici (Je ne le résume 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écidez de changer votre classe Money
et se débarrasser des attributs dollars
et cents
mais décider de suivre seulement le montant total de cents:
class Money:
def __init__(self, dollars, cents):
self.total_cents = dollars * 100 + cents
si l'utilisateur mentionné ci-dessus essaie maintenant d'exécuter sa bibliothèque comme avant
money = Money(27, 12)
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
il en résultera une erreur
AttributeError: 'l'Argent' objet n'a pas d'attribut 'dollars'
cela signifie que maintenant tous ceux qui comptent sur votre classe originale Money
devrait changer toutes les lignes de code où dollars
et cents
sont utilisés, ce qui peut être très douloureux... Alors, comment cela pourrait-il être évité? En utilisant @property
!
C'est ainsi:
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
lorsque nous appelons aujourd'hui à partir 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.
il va fonctionner 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 à savoir que la bibliothèque nous dépendons changé.
aussi le setter
amende de travaux:
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.