Using the calculated attribute cache

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

Guess you like

Origin www.cnblogs.com/jeffrey-yang/p/12115944.html