Use of @property decorator in python

Keywords: Python Attribute less

[TOC]

1. Raise questions

When binding attributes to a class instance, if we expose them directly, it's easy to write, but there's no way to check the parameters, so you can change your performance arbitrarily, even if it's of the wrong type.

class Student(object):

    def __init__(self, score):
        self.score = score


if __name__ == '__main__':
    s = Student(100)
    print(s.score)
    s.score = 50
    print(s.score)
    s.score = 'abc'
    print(s.score)

------------------------------

>>> 100
>>> 50
>>> abc

2. Initial improvement

The above example is obviously illogical. To limit the scope of the score, you can set the score by a set_score() method and get the score by a get_score() method, so that you can check the parameters in the set_score() method.

class Student(object):

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer !')
        if value < 0 or value > 100:
            raise ValueError('score must between 0-100 !')
        self._score = value

    def get_score(self):
        return self._score


if __name__ == '__main__':
    s = Student()
    s.set_score(50)
    print(s.get_score())
    s.set_score('abc')

------------------------------

>>> 50
>>> Traceback (most recent call last):
  File "/Users/luyuze/projects/myflask/App/test.py", line 18, in <module>
    s.set_score('abc')
  File "/Users/luyuze/projects/myflask/App/test.py", line 6, in set_score
    raise ValueError('score must be an integer !')
ValueError: score must be an integer !

Now, if you operate on any Student instance, you can't set the score as you like.

3. Use @property

Although the above invocation methods can already achieve related functions, they are slightly more complex to use. Setting and getting attributes need to be done by invocation methods, not directly using attributes so concise and clear.

So is it possible to check parameters and access class variables in a simple way like attributes?For python to be perfect, this is a must!

Next, we'll use python's built-in decorator @property.

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer !')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 - 100 !')
        self._score = value


if __name__ == '__main__':
    s = Student()
    s.score = 50 # Actually converted to s.set_score()
    print(s.score) # Actually converted to s.get_score()
    s.score = 101

------------------------------

>>> 50
>>> Traceback (most recent call last):
  File "/Users/luyuze/projects/myflask/App/test.py", line 21, in <module>
    s.score = 101
  File "/Users/luyuze/projects/myflask/App/test.py", line 13, in score
    raise ValueError('score must between 0 - 100 !')
ValueError: score must between 0 - 100 !

4. Resolve @property

The implementation of @property is complicated. First, let's look at how to use it to make a getter method an attribute by adding @property. At this point, @property itself creates another decorator, @score.setter, which is responsible for turning a setter method into an attribute assignment. So we have the attribute operation shown in the example above.

Notice this amazing @property, and when we manipulate an instance property, we know that it is likely that the property is not exposed directly, but rather implemented using getter and setter methods.

We can also define a read-only property, a getter-only method, or a read-only property if we don't define a setter method.

import datetime


class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        if not isinstance(value, int):
            raise ValueError('birth must be an integer !')
        self._birth = value

    @property
    def age(self):
        return datetime.datetime.now().year - self._birth


if __name__ == '__main__':
    s = Student()
    s.birth = 1995
    print(s.age)
    s.age = 25

------------------------------

>>> 24
>>> Traceback (most recent call last):
  File "/Users/luyuze/projects/myflask/App/test.py", line 25, in <module>
    s.age = 25
AttributeError: can't set attribute

The above birth is a read-write property, and age is a read-only property because it can be calculated from the birth and the current year.

5. Summary

@property is widely used in class definitions to allow callers to write short code while guaranteeing the necessary checks on parameters so that the program runs with less chance of error.

Posted by CooKies37 on Thu, 07 Nov 2019 18:49:26 -0800