社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

让Python中类的属性具有惰性求值的能力

Python中文社区 • 6 年前 • 734 次点击  

起步

我们希望将一个只读的属性定义为 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__() 才会被调用。

更多描述可见文档:

https://docs.python.org/3/reference/datamodel.html?#object.__get__

这种惰性求值的方法在很多模块中都会使用,比如django中的 cached_property:

使用上与例子一致,如表单中的 changed_data :

讨论

在大部分情况下,让属性具有惰性求值能力的全部意义就在于提升程序性能。当不需要这个属性时就能避免进行无意义的计算,同时又能阻止该属性重复进行计算。

本文的技巧中有一个潜在的缺点,就是计算出的值后就变成可变的(mutable)。

>>> c.area
78.53
>>> c.area = 3
>>> c.area
3

如果考虑可变性的问题,可以使用另一种实现方式,但执行效率会稍打折扣:

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 函数来处理,这比直接在实例字典中查找相应的值要慢一些。

 作者:weapon,闲来笑浮生悬笔一卷入毫端,朱绂临身可与言者不过二三。

赞 赏 作 者


投稿邮箱:pythonpost@163.com

欢迎点击申请成为专栏作者Python中文社区新专栏作者计划

Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以公安部、工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

▼点击下方阅读原文,进入学习


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/hcwlujUOaM
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/22706
 
734 次点击