来源: https://zhuanlan.zhihu.com/p/42094516 起步 我们希望将一个只读的属性定义为 property
属性方法,只有在访问它时才进行计算,但是,又希望把计算出的值缓存起来,不要每次访问它时都重新计算。
解决方案 定义一个惰性属性最有效的方法就是利用描述符类来完成它,示例如下:
class lazyproperty : def __init__ (self, fun ): self.fun = fun def __get__(self, instance, owner): if instance is None: return self value = self.fun (instance) setattr(instance, self.fun .__name__, value) return value
要使用这个工具,可以像下面的方式来使用它:
class Circle : def __init__ (self , radius) : self .radius = radius @lazyproperty def area (self ) : print('Computing area' ) return 3.1415 * self .radius ** 2 c = Circle(5 )
print(c.area) print(c.area)
可以看出,这里的实例方法 area()
只会被调用一次。
为什么会这样? 如果类中定义了 __get__()
、__set__()
、__delete__()
中的任何方法,那么这个就被成为描述符(descriptor)。
一般情况下(我是说一般情况下),访问属性的默认行为是从对象的字典中获取,并沿着一个查找链的顺序进行搜索,比如对于 a.x
有一个查找链,从 a.__dict__['x']
然后是 type(a).__dict__['x']
,再继续通过 type(a)
的基类开始。
而如果查找的值是一个描述符对象,则会覆盖这个默认的搜索行为,优先采用描述符的行为,这个行为会因为如果调用而有些不同。这里就只说明例子中的情况。
如果描述符绑定的对象实例,a.x
则转换为调用: type(a).__dict__['x'].__get__(a, type(a))
。
当一个描述符之定义 __get__()
方法,则它的绑定关系比一般情况下要弱化很多。特别是,只有当被访问的属性不存在对象字典中时,__get__()
才会被调用。
更多描述可见文档:
Data model - Python 3.7.0 documentationdocs.python.org
这种惰性求值的方法在很多模块中都会使用,比如django中的 cached_property
:
使用上与例子一致,如表单中的 changed_data
:
讨论 在大部分情况下,让属性具有惰性求值能力的全部意义就在于提升程序性能。当不需要这个属性时就能避免进行无意义的计算,同时又能阻止该属性重复进行计算。
本文的技巧中有一个潜在的缺点,就是计算出的值后就变成可变的(mutable)。
>> > c.area78.53 >> > c.area = 3 >> > c.area3
如果考虑可变性的问题,可以使用另一种实现方式,但执行效率会稍打折扣:
def lazyproperty (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
如果使用这种方式,就会发现 set
操作是不允许的,所有的 get 操作都必须经由属性的 getter
函数来处理,这比直接在实例字典中查找相应的值要慢一些。
参考 https://docs.python.org/3/reference/datamodel.html?#object.__get__
《Python Cookbook 第三版》