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.

687
demandé sur Martijn Pieters 2013-06-27 00:47:15

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__)
758
répondu Martijn Pieters 2016-07-15 19:05:08

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)
106
répondu J0HN 2016-07-15 20:45:42

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.

64
répondu glglgl 2013-06-26 20:53:15

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'
54
répondu AlexG 2017-02-15 00:46:29

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
25
répondu Bill Moore 2017-05-24 18:38:10

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

6
répondu Leo Skhrnkv 2018-03-23 14:47:53

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 -

  1. une méthode utilisée pour obtenir une valeur est décorée de"@property".
  2. 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".
  3. , 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

1
répondu Divyanshu Rawat 2018-09-17 00:01:12

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 .

0
répondu nvd 2017-07-13 09:20:51

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

0
répondu Acecool 2018-08-07 11:08:27

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.
0
répondu Cleb 2018-09-24 06:13:37