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 条评论
登录 后参与评论

相关文章

来自专栏精讲JAVA

深入探索 Java 热部署

在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加...

611
来自专栏python爬虫日记

python 下字符串格式时间比较

python 下有多个有关时间的模块,分别是time、datetime、calendar,今天重点讨论下time写法。

722
来自专栏技巅

Thrift之TProcess类体系原理及源码详细解析

1642
来自专栏快乐八哥

使用jQuery封装实用函数

一、引言 项目开发中,前端会有一个辅助工具类的js文件,比如cookie的操作,团队成员自己封装的方法。大多数时候,我们开发人员自己都是写一个全局函数,不考虑后...

2005
来自专栏walterlv - 吕毅的博客

(持续整理中)Visual Studio 中 C# 代码分析规则集中每一项的含义 (stylecop ruleset)

发布于 2018-02-07 13:55 更新于 2018-03...

612
来自专栏为数不多的Android技巧

Android Studio你不知道的调试技巧

写代码不可避免有Bug,通常情况下除了日志最直接的调试手段就是debug;那么你的调试技术停留在哪一阶段呢?仅仅是下个断点单步执行吗?或者你知道 Evaluat...

621
来自专栏编程

如何修改动态代理的私有变量

最近在写一个 Spring Controller 的 JUnit 单元测试时,需要将一个Mock对象塞入到Controller的私有成员变量中,发现怎么都塞不成...

1829
来自专栏pangguoming

SpringBoot实战 之 异常处理篇

在互联网时代,我们所开发的应用大多是直面用户的,程序中的任何一点小疏忽都可能导致用户的流失,而程序出现异常往往又是不可避免的,那该如何减少程序异常对用户体验的影...

3577
来自专栏java一日一条

java设计模式-观察者模式

基本概念:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己...

852
来自专栏java一日一条

用Java实现一个通用并发对象池

这篇文章里我们主要讨论下如何在Java里实现一个对象池。最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来...

432

扫码关注云+社区