python学习笔记6.6-类的惰性属性

我们想将一个只读属性定义为property属性方法,只有在访问它时才参与计算。同时,一旦访问了该属性,希望把计算出来的值缓存起来,不要每次访问它时都要重新计算。这样就能很大程度上提升程序的性能。定义一个惰性属性最简单的方法就是利用描述符来完成。

#define a lazyproperty
class Lazyproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self,instance,cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance,self.func.__name__,value)
            return value

#Example
import math
class Circle:
    def __init__(self,radius):
        self.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)
print(c.radius)
print('calling c.area the first time:')
print(c.area)
print('calling c.area the second time:')
print(c.area)

打印输出:
4.0
calling c.area the first time:
Computing area
25.132741228718345
calling c.area the second time:
25.132741228718345

从示例中可以很清楚的看出,第一次调用c.area时计算过程被执行,第二次调用它的时候,计算过程没有执行,是因为计算一次之后,它的值就被储存起来了,第二次直接拿出来用,从而加快了程序的运行。

前面提到描述符的时候讲过,当吧描述符放到类的定义体中的时候,访问它的属性会出发get(),set(),delete()方法。但是,如果一个描述符只定义了get()方法,则它的绑定关系比一般情况要弱化的多。特别是,只有当被访问的属性不在底层的实例字典中时,_get_()方法会得到调用。

但是,这种技术有一个潜在是bug,一旦使用了这种方法,计算的值就会变成可变的了。

c.area = 66
print(c.area)
打印输出:
66

如果考虑到可变性,可以使用一种方法去修复这个bug,但是同时执行效率也会大大的降低。

def lazyproperty(func):
    name = '_lazy_' + func.__name__
    @property
    def lazy(self):
        if hasattr(self,name):
            return getattr(self,name)
        else:
            value = func(self)
            setattr(self,name,value)
            return value
    return lazy

#Example
import math
class Circle:
    def __init__(self,radius):
        self.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)
print(c.radius)
print('calling c.area the first time:')
print(c.area)
print('calling c.area the second time:')
print(c.area)

打印输出:
4.0
calling c.area the first time:
Computing area
25.132741228718345
calling c.area the second time:
25.132741228718345

从该实例中可以发现,达到了同样的效果。

c.area = 66
print(c.area)

Traceback (most recent call last):
  File "D:/home/WX/test_lazyproperty.py", line 32, in <module>
    c.area = 66
AttributeError: can't set attribute

之后改变c.area的值,发现报错,不能被修改。这样就修复了第一种方法中计算值可以被外部改变的bug。这种方法的缺点就是所有的get操作都必须经由属性的getter函数来处理。这比直接在实例字典中查找相应的值要慢一些。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏尾尾部落

[剑指offer] 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为...

782
来自专栏liulun

Nim教程【二】

第一篇教程1秒内就被管理员从首页踢掉了 管理员嫌内容太少,没有含金量,这次多写一些。 这应该是国内第一个关于Nim入门的系列教程 好,闲话休提,言归...

22710
来自专栏影子

jsp的C标签一般使用方法以及js接收servlet中的对象及对象数字

3578
来自专栏听雨堂

Python学习笔记(3):数据集操作-列的统一操作

对数据库查询,将得到一个数据集: rs=AccessDB.GetData("select * from log where f_code='600259' ...

2096
来自专栏xingoo, 一个梦想做发明家的程序员

MFC中注释含义

下面是 CStdioFile 类的部分列表,其中使用了 MFC 在其类中按类成员的用法划分它们时所采用的大多数标准注释: class CStdioFile :...

1877
来自专栏海天一树

图的广度优先搜索

广度优先搜索算法是最简便的图的搜索算法之一,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索...

1082
来自专栏salesforce零基础学习

salesforce 零基础开发入门学习(七)PickList的value值获取

    之前介绍过PickList类型的声明以及赋值,但是如何取出呢?一个sObject对象可以理解为一条数据。通过sObject直接取恐怕很难做到,因为他只会...

2075
来自专栏小鹏的专栏

tf API 研读3:Building Graphs

tensorflow是通过计算图的方式建立网络。 比喻说明: 结构:计算图建立的只是一个网络框架。编程时框架中不会出现任何的实际值,所有权重(weight)和偏...

2317
来自专栏程序你好

C# 发展历史及版本新功能介绍

762
来自专栏企鹅号快讯

Python进阶系列连载(5)——生成器(上)

作者:王大伟 Python爱好者社区唯一小编 博客:https://ask.hellobi.com/blog/wangdawei 生成器 还记得在迭代器里我们...

38310

扫码关注云+社区