社区所有版块导航
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猫 • 4 年前 • 599 次点击  
👆 Python猫” ,一个值得加星标的公众号

剧照 | 《清平乐》

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

参考

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

  2. 《Python Cookbook 第三版》

优质文章,推荐阅读:

Python进阶:迭代器与迭代器切片

GIL 已经被杀死了么?

如何美观地打印 Python 对象?这个标准库可以简单实现

学编程这么久,还傻傻分不清什么是方法(method),什么是函数(function)?

感谢创作者的好文
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/61745
 
599 次点击  
文章 [ 1 ]  |  最新文章 2 年前