Question Comment fonctionne le décorateur @property?


Je voudrais comprendre comment la fonction intégrée property travaux. Ce qui me trouble est que property peut également être utilisé comme décorateur, mais il ne prend des arguments que lorsqu'il est utilisé comme fonction intégrée et non lorsqu'il est utilisé comme décorateur.

Cet exemple provient du 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.")

propertyLes arguments de getx, setx, delx et une chaîne de doc.

Dans le code ci-dessous property est utilisé comme décorateur. L'objet en est le x fonction, 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 sont les x.setter et x.deleter décorateurs créés? Je suis confus.


660
2018-06-26 20:47


origine


Réponses:


le property() fonction renvoie un spécial objet descripteur:

>>> property()
<property object at 0x10ff07940>

C'est cet objet qui a supplémentaire 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 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 le @decorator la syntaxe est juste 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)

alors foo la fonction est remplacée par property(foo), que nous avons vu ci-dessus est un objet spécial. Puis quand vous utilisez @foo.setter(), ce que tu fais s'appelle ça property().setter méthode que je vous ai montré 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.

D'abord, nous créons des 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 le .setter() méthode 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 le .deleter() méthode:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Last but not least, le property objet agit comme objet descripteur, donc il a .__get__(), .__set__() et .__delete__() méthodes pour accrocher dans l'attribut d'instance 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 descripteur Howto comprend un implémentation d'un échantillon python pur 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__)

740
2018-06-26 20:54



La documentation dit C'est juste un raccourci pour créer des 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)

102
2018-06-26 20:52



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.

L'étape suivante consisterait à étendre cette propriété avec un setter et un deleter. Et cela arrive avec les méthodes appropriées:

@x.setter
def x(self, value): ...

retourne une nouvelle propriété qui hérite tout de l'ancien x plus le setter donné.

x.deleter fonctionne de la même manière.


62
2018-06-26 20:53



Voici un exemple minimal de comment @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'

Autrement 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'

48
2018-02-15 00:46



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)

Ce 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

24
2018-05-24 18:38



J'ai lu tous les messages ici et réalisé que nous pourrions avoir besoin d'un exemple réel, Pourquoi, en fait, nous avons @property? Donc, considérez une application Flask où vous utilisez un 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 l'attribut "caché" passworden utilisant @property ce qui déclenche AttributeError assertion lorsque vous essayez d'y accéder directement, alors que nous avons utilisé @ property.setter pour définir la variable d'instance réelle password_hash.

Maintenant en 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()
...

Attribut de notification password cela 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 poseriez la question, mais c'est un sujet différent lié aux formes Flask).

J'espère que cet exemple sera utile


4
2018-03-23 14:47



Une propriété peut être déclarée de deux façons.

  • Création du getter, mise en place de méthodes pour un attribut puis passage de celles-ci en tant qu'argument à propriété fonction
  • En utilisant le @propriété décorateur.

Vous pouvez jeter un oeil à quelques exemples sur lesquels j'ai écrit propriétés en python.


1
2017-07-13 09:20