Python 黑魔法【延迟计算属性】与【动态计算属性】

那么众所周知,Pyhon延迟计算属性在很多技术博客上已经讲述过了,这种方法,最初是在Cookbook上讲的,并且根据该案例作一些小的修改,由此得到动态计算属性,此篇文章不仅重现该案例,下面的部分内容可能与其他博主或者官方相似,此篇文章主要是为了引出更多的方法。

首先先讲一下延迟计算属性吧,延迟计算属性所解决的问题是:你想将一个只读属性定义成一个property,并且只在访问的时候才会计算结果。 但是一旦被访问后,你希望结果值被缓存起来,不用每次都去计算。 这种方式主要也是为了提升性能,只有在初次调用的时候才会计算一次,后续调用也不会进行计算

1、延迟计算属性

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

通用下面这种方式使用它

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)
>>> c.radius
4.0
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
>>>

仔细观察你会发现消息 Computing areaComputing perimeter 仅仅出现一次。

2、延迟计算属性【私有属性】

那么这些属性的缺陷是它们是可以被修改的,如果你考虑安全方面的问题,可以 用如下方式实现。

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

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)
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.area = 25
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>

3、动态计算属性【私有属性】

通过上述方法设置后,还是会存在一些缺陷,如果你即使想设置属性为私有属性,又想动态计算属性,以便于后续修改属性后,能改在调用对应的方法后,获得动态的私有属性值,那么博主在这里有一个简单的方法。

import math


class Circle:

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

    def __getattr__(self, item):
        name = "__" + item
        if name in self.__dict__.keys():
            return self.__dict__.get(name)
        else:
            return self.__dict__.get(item)

    def __setattr__(self, item, value):
        name = "__" + item
        if name in self.__dict__.keys():
            raise AttributeError("can't set attribute")
        else:
            self.__dict__.update({item: value})

    def calc_prop(self, radius):
        area = math.pi * radius ** 2
        perimeter = 2 * math.pi * radius
        localss = locals()
        localss.pop("self")
        localss = {"__" + k: v for k, v in localss.items()}
        self.__dict__.update(localss)
        return self.__dict__

原理也很简单,首先在calc_prop方法里传入radius后,这里需要注意的是,获得局部变量后,需要在属性前添加__变为私有属性,把在局部变量里已经计算好的属性赋予__dict__即可,但是这里需要注意的是,即使赋值好后,类属性依然不是私有属性,所以需要通过手动重写 getattr 函数和 setattr 函数。

下面是交互结果

c = Circle(4.0)
print(c.radius)
print(c.__dict__)
print(c.calc_prop(3.0))
print("area", c.area)
c.area = 20
4.0
{'radius': 4.0}
{'radius': 4.0, '__radius': 3.0, '__area': 28.274333882308138, '__perimeter': 18.84955592153876}
area 28.274333882308138
Traceback (most recent call last):
  File "D:/Desktop/neimenggu/catch.py", line 38, in <module>
    c.area = 20
  File "D:/Desktop/neimenggu/catch.py", line 19, in __setattr__
    raise AttributeError("can't set attribute")
AttributeError: can't set attribute
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码王吴彦祖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值