美文网首页
python 将计算结果保留到缓存中

python 将计算结果保留到缓存中

作者: SkTj | 来源:发表于2019-12-03 13:46 被阅读0次

定义一个延迟属性的一种高效方法是通过使用一个描述器类,如下所示:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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

</pre>

你需要像下面这样在一个类中使用它:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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

</pre>

下面在一个交互环境中演示它的使用:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> 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

</pre>

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

讨论

很多时候,构造一个延迟计算属性的主要目的是为了提升性能。 例如,你可以避免计算这些属性值,除非你真的需要它们。 这里演示的方案就是用来实现这样的效果的, 只不过它是通过以非常高效的方式使用描述器的一个精妙特性来达到这种效果的。

正如在其他小节(如8.9小节)所讲的那样,当一个描述器被放入一个类的定义时, 每次访问属性时它的 __get__()__set__()__delete__() 方法就会被触发。 不过,如果一个描述器仅仅只定义了一个 __get__() 方法的话,它比通常的具有更弱的绑定。 特别地,只有当被访问属性不在实例底层的字典中时 __get__() 方法才会被触发。

lazyproperty 类利用这一点,使用 __get__() 方法在实例中存储计算出来的值, 这个实例使用相同的名字作为它的property。 这样一来,结果值被存储在实例字典中并且以后就不需要再去计算这个property了。 你可以尝试更深入的例子来观察结果:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> c = Circle(4.0)

Get instance variables

vars(c)
{'radius': 4.0}

Compute area and observe variables afterward

c.area
Computing area
50.26548245743669
vars(c)
{'area': 50.26548245743669, 'radius': 4.0}

Notice access doesn't invoke property anymore

c.area
50.26548245743669

Delete the variable and see property trigger again

del c.area
vars(c)
{'radius': 4.0}
c.area


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

## 讨论

很多时候,构造一个延迟计算属性的主要目的是为了提升性能。 例如,你可以避免计算这些属性值,除非你真的需要它们。 这里演示的方案就是用来实现这样的效果的, 只不过它是通过以非常高效的方式使用描述器的一个精妙特性来达到这种效果的。

正如在其他小节(如8.9小节)所讲的那样,当一个描述器被放入一个类的定义时, 每次访问属性时它的 __get__()__set__()__delete__() 方法就会被触发。 不过,如果一个描述器仅仅只定义了一个 __get__() 方法的话,它比通常的具有更弱的绑定。 特别地,只有当被访问属性不在实例底层的字典中时 __get__() 方法才会被触发。

lazyproperty 类利用这一点,使用 __get__() 方法在实例中存储计算出来的值, 这个实例使用相同的名字作为它的property。 这样一来,结果值被存储在实例字典中并且以后就不需要再去计算这个property了。 你可以尝试更深入的例子来观察结果:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> c = Circle(4.0)
>>> # Get instance variables
>>> vars(c)
{'radius': 4.0}

>>> # Compute area and observe variables afterward
>>> c.area
Computing area
50.26548245743669
>>> vars(c)
{'area': 50.26548245743669, 'radius': 4.0}

>>> # Notice access doesn't invoke property anymore
>>> c.area
50.26548245743669

>>> # Delete the variable and see property trigger again
>>> del c.area
>>> vars(c)
{'radius': 4.0}
>>> c.area
Computing area
50.26548245743669

</pre>

这种方案有一个小缺陷就是计算出的值被创建后是可以被修改的。例如:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> c.area
Computing area
50.26548245743669

c.area = 25
c.area
25

</pre>

如果你担心这个问题,那么可以使用一种稍微没那么高效的实现,就像下面这样:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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
</pre>

如果你使用这个版本,就会发现现在修改操作已经不被允许了:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> 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
</pre>

相关文章

  • python 将计算结果保留到缓存中

    定义一个延迟属性的一种高效方法是通过使用一个描述器类,如下所示: class lazyproperty:def i...

  • 《隐性逻辑 》书评背后逻辑与决定

    18/(23-14),先计算出23-14,计算结果为9,缓存,再将18/9,更改计算结果2。将调出信息、缓存、更改...

  • SparkCore之RDD缓存与RDD CheckPoint

    RDD缓存 RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() ...

  • Java并发编程总结

    volatile实质是内存的可见性: cpu缓存包括:寄存器、L1、L2等,把线程的计算结果首先缓存在CPU缓存中...

  • python文件open参数属性

    Python文件属性 python打开文件后为什么要关闭 ? 将写缓存同步到磁盘; linux系统中每个进程打开的...

  • Spark-缓存

    RDD通过persist方法或cache方法可以将前面得计算结果进行缓存,默认情况下persist会将数据序列化后...

  • 『JavaScript专题』之函数记忆

    定义 函数记忆是指将上次的计算结果缓存起来,当下次调用时,如果遇到相同的参数,就直接返回缓存中的数据。 举个例子:...

  • JS优化计算结果Memoization

    Memoization用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中...

  • Memoization技术优化递归的重复计算

    Memoization用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中...

  • Javascript缓存函数&柯里化&偏函数

    缓存函数 memorizition 定义:将上次的计算结果缓存起来,当下次调用时,如果遇到相同的参数,就直接返回缓...

网友评论

      本文标题:python 将计算结果保留到缓存中

      本文链接:https://www.haomeiwen.com/subject/pwdcpctx.html