经典案例-惰性运算

惰性计算(Lazy evaluation),是指仅仅在真正需要执行的时候才计算表达式的值。充分利用其特性可以带来很多便利。

  1. 避免不必要的计算,带来性能的提升。
  2. 节省空间,使得无线循环的数据结构成为可能。

将描述符与惰性运算结合起来。我们直接看代码:

class LazyProperty:
    def __init__(self, fun):
        self.fun = fun

    def __get__(self, instance, owner):
        print("get被调用")
        if instance is None:
            return self
        value = self.fun(instance)
        # 三个参数,第一个预设置的对象,第二个是预设置对象key,第三个是名下的value
        setattr(instance, self.fun.__name__, value)  # 给instance设置个属性{"self.fun__name__": value},self.fun指向的是area那个函数,
        # self.fun__name__的意思就是获取这个函数的名字,所以这句的意思就是给a指向的实例对象是设置一个属性{"area": value}
        return value

    # def __set__(self, instance, value):
    #     pass

class ReadonlyNumber:
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        raise AttributeError(f"{self.value} is not modifiable")


class Circle:
    pi = ReadonlyNumber(3.14)

    def __init__(self, radius):
        self.radius = radius  # 4

    @LazyProperty  # 等价于 area = LazyProperty(area),即LazyProperty创建了一个实例对象 = area
    def area(self):
        print("计算面积")
        return self.pi * self.radius ** 2


a = Circle(4)
print(a.__dict__)
print(a.area)
print(a.__dict__)
print(a.area)  # 这里要继续调用的话就直接取值,不用在进行计算了。

运行结果:
{'radius': 4}
get被调用
计算面积
50.24
{'radius': 4, 'area': 50.24}
50.24

进程已结束,退出代码0

现在来将这个代码拆开一步一步的看:

  1. 执行a = Circle(4),给 a 指向的实例对象添加一个radius的属性
  2. 执行print(a.area),解释器就会去a的实例对象中寻找area属性,发现没有,于是去a的实例对象的父类中去寻找,发现areaLazyProperty修饰器所修饰,因此这时就相当于
    area = LazyProperty(area),使得area指向一个类,所以area是一个描述符。
  3. 获取area,于是调用了LazyProperty中的get方法,执行value = self.fun(instance)其中self.fun = fun于是self.fun就是area,instance就是实例对象a,所以上面的语句为 value = area(a).
  4. 于是返回找area函数area函数中定义了一个self.pi * self.radius ** 2,此时radius已知,于是找pipi 描述符指向一个类pi = ReadonlyNumber(3.14),于是直接去把值用。
  5. 执行完area函数后,返回get,执行setattr(instance, self.fun.__name__, value)self.fun__name__的意思就是获取这个函数的名字,所以这句的意思就是给a指向的实例对象是设置一个属性{"area": value}
  6. 于是代码执行完毕,而且在a中创建了一个属性area用来存储value
  7. 再次调用a.area是就会发生描述符名和属性名冲突的情况,但此时area指向的实例对象中只有__get__,即area是一个非数据描述符,所以在与属性名冲突,属性名的优先级更高,所以print(a.area)返回的是属性值50.24, 而不是再把函数运行一遍。

如果我么将在LazyProperty中再加一个set函数,那么根据描述符和属性名冲突时的优先顺序,运行的结果就会发生区别了。

class LazyProperty:
    def __init__(self, fun):
        self.fun = fun

    def __get__(self, instance, owner):
        print("get被调用")
        if instance is None:
            return self
        value = self.fun(instance)
        # 三个参数,第一个预设置的对象,第二个是预设置对象key,第三个是名下的value
        setattr(instance, self.fun.__name__, value)  # 给instance设置个属性{"self.fun__name__": value},self.fun指向的是area那个函数,
        # self.fun__name__的意思就是获取这个函数的名字,所以这句的意思就是给a指向的实例对象是设置一个属性{"area": value}
        return value

    def __set__(self, instance, value):
        pass

class ReadonlyNumber:
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        raise AttributeError(f"{self.value} is not modifiable")


class Circle:
    pi = ReadonlyNumber(3.14)

    def __init__(self, radius):
        self.radius = radius  # 4

    @LazyProperty  # 等价于 area = LazyProperty(area),即LazyProperty创建了一个实例对象 = area
    def area(self):
        print("计算面积")
        return self.pi * self.radius ** 2


a = Circle(4)
print(a.__dict__)
print(a.area)
print(a.__dict__)
print(a.area)  # 这里要继续调用的话就直接取值,不用在进行计算了。

运行结果:
{'radius': 4}  # 第一次
get被调用
计算面积
50.24
{'radius': 4}  #第二次
get被调用
计算面积
50.24

进程已结束,退出代码0

从上述代码可以看出,调用了两次print(a.area)函数就执行了两次,所以没有完成惰性运算,这就是将非数据描述符LazyProperty变成数据描述符的区别了。
下面时流程图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值