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