How does the @property decorator work in Python?

后端 未结 13 2105
闹比i
闹比i 2020-11-21 04:49

I would like to understand how the built-in function property works. What confuses me is that property can also be used as a decorator, but it only

相关标签:
13条回答
  • 2020-11-21 05:26

    A property can be declared in two ways.

    • Creating the getter, setter methods for an attribute and then passing these as argument to property function
    • Using the @property decorator.

    You can have a look at few examples I have written about properties in python.

    0 讨论(0)
  • 2020-11-21 05:27

    I read all the posts here and realized that we may need a real life example. Why, actually, we have @property? So, consider a Flask app where you use authentication system. You declare a model User in 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)
    

    In this code we've "hidden" attribute password by using @property which triggers AttributeError assertion when you try to access it directly, while we used @property.setter to set the actual instance variable password_hash.

    Now in auth/views.py we can instantiate a User with:

    ...
    @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()
    ...
    

    Notice attribute password that comes from a registration form when a user fills the form. Password confirmation happens on the front end with EqualTo('password', message='Passwords must match') (in case if you are wondering, but it's a different topic related Flask forms).

    I hope this example will be useful

    0 讨论(0)
  • 2020-11-21 05:29

    The property() function returns a special descriptor object:

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

    It is this object that has extra methods:

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

    These act as decorators too. They return a new property object:

    >>> property().getter(None)
    <property object at 0x10ff079f0>
    

    that is a copy of the old object, but with one of the functions replaced.

    Remember, that the @decorator syntax is just syntactic sugar; the syntax:

    @property
    def foo(self): return self._foo
    

    really means the same thing as

    def foo(self): return self._foo
    foo = property(foo)
    

    so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.

    The following sequence also creates a full-on property, by using those decorator methods.

    First we create some functions and a property object with just a 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
    

    Next we use the .setter() method to add a setter:

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

    Last we add a deleter with the .deleter() method:

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

    Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:

    >>> class Foo: pass
    ... 
    >>> prop.__get__(Foo(), Foo)
    Get!
    >>> prop.__set__(Foo(), 'bar')
    Set to 'bar'!
    >>> prop.__delete__(Foo())
    Delete!
    

    The Descriptor Howto includes a pure Python sample implementation of the property() type:

    class Property:
        "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__)
    
    0 讨论(0)
  • 2020-11-21 05:31

    The best explanation can be found here: Python @Property Explained – How to Use and When? (Full Examples) by Selva Prabhakaran | Posted on November 5, 2018

    It helped me understand WHY not only HOW.

    https://www.machinelearningplus.com/python/python-property/

    0 讨论(0)
  • 2020-11-21 05:33

    Documentation says it's just a shortcut for creating readonly properties. So

    @property
    def x(self):
        return self._x
    

    is equivalent to

    def getx(self):
        return self._x
    x = property(getx)
    
    0 讨论(0)
  • 2020-11-21 05:35

    The first part is simple:

    @property
    def x(self): ...
    

    is the same as

    def x(self): ...
    x = property(x)
    
    • which, in turn, is the simplified syntax for creating a property with just a getter.

    The next step would be to extend this property with a setter and a deleter. And this happens with the appropriate methods:

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

    returns a new property which inherits everything from the old x plus the given setter.

    x.deleter works the same way.

    0 讨论(0)
提交回复
热议问题