In Python, a method using @property property becomes decorators. Sometimes, in order to improve performance, to be calculated at the time of the first call only method property, the value of the subsequent use of the cache.
In this case, a class may be used decorator, as follows:
class LazyProperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
Use on __get__, __set__, __delete__ other methods, refer to the descriptor reference to the article, more content.
It can be used as follows:
import math
class Circle:
def __init__(self, radius):
this.radius = radius
@LazyProperty
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@LazyProperty
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
>>>
As can be seen, on the first call c.area or c.perimeter, use LazyProperty of __get__ method, and in the second and subsequent calls, the direct value has been calculated. The reason is that when using @LazyProperty, area and perimeter at this time becomes the object descriptor, during attribute access, if __get__ defined class descriptor, __set__, __delete__, the respective methods are invoked. Call to order and only if the relevant definitions __get__. If only defined __get__, compared with the non-data descriptor (non-data descriptor), or if the defined __set__ __delete__, compared with data descriptor (data descriptor). Detailed documentation may refer to Implementing Descriptors .
In the first call area and perimeter, it will calculate the actual value, then call setattr method will function names and values stored in __dict__ instance. When the next call, and because only defines __get__ method, not yo-defined __set__ home and __delete__, it is the only non-data descriptor descriptor. Non-data descriptor when accessing properties, will be covered by the underlying property of the dictionary object.
The important points to remember are:
- descriptors are invoked by the __getattribute__() method
- overriding __getattribute__() prevents automatic descriptor calls
- object.__getattribute__() and type.__getattribute__() make different calls to __get__().
- data descriptors always override instance dictionaries.
- non-data descriptors may be overridden by instance dictionaries.
A potential disadvantage of this implementation is that, if the following operation, the calculated value can be changed, resulting in distortion of the data:
>>> c.area
Computing area
50.26548245743669
>>> c.area = 25
>>> c.area
25
>>>
Some might say, you can add __set__ LazyProperty method, internal method directly thrown raise AttributeError ( "Can not change the value"). Add __set__ but at the same time, access the properties will change the priority at this time a cache miss. Perimeter and when calling area, call each time __get__ underlying every time calc.
In this case, the method may be used decorators, as follows:
def lazy_property(func):
name = '_lazy_' + func.__name__
@property
def lazy(self):
if hasattr(self, name):
return getattr(self, name)
value = func(self)
setattr(self, name, value)
return value
return lazy
class Circle:
def __init__(self, radius):
this.radius = radius
@lazy_property
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@lazy_property
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius