说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 内置模块 functools 提供的高阶函数 @functools.cached_property
将类的方法转换为一个属性,该属性的值计算一次,然后在实例的生命周期中将其缓存作为普通属性。与 property() 类似,但添加了缓存,对于在其他情况下实际不可变的高计算资源消耗的实例特征属性来说该函数非常有用。它是 Python 3.8 新版功能。
@cached_property
是一个装饰器(decorator),它将类的方法转换为一个属性,该属性的值只计算一次,然后缓存为普通属性。因此,只要实例持续存在,缓存结果就会可用,我们可以将该方法用作类的属性。它是 Python 中 functools 模块的一部分。
先看以下示例:
from functools import cached_property
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = tuple(sequence_of_numbers)
@cached_property
def stdev(self):
return statistics.stdev(self._data)
类 DataSet 的方法 DataSet.stdev()
在生命周期内变成了属性 DataSet.stdev
。
缓存的 cached_property()
的机制与 property()
有些不同。除非定义了setter,否则常规属性会阻止属性写入。相反,缓存的 cached_property 允许写入。
缓存的 cached_property 装饰器仅在查找时运行,并且仅在同名属性不存在时运行。当它运行时,cached_property 会写入具有相同名称的属性。后续的属性读取和写入优先于缓存的 cached_property 方法,其工作方式与普通属性类似。
缓存的值可通过删除该属性来清空。 这允许 cached_property 方法再次运行。
缓存是 CPU 内部的一种高速内存,用于加速对数据和指令的访问。因此,缓存是一个可以快速访问的地方,结果可以计算并存储一次,从下一次开始,无需再次计算即可访问结果。因此,在计算费用昂贵的情况下,它非常有用。
这个装饰器会影响 PEP 412 键共享字典的操作。 这意味着相应的字典实例可能占用比通常时更多的空间。
而且,这个装饰器要求每个实例上的 __dict__
是可变的映射。 这意味着它将不适用于某些类型,例如元类(因为类型实例上的 __dict__
属性是类命名空间的只读代理),以及那些指定了 __slots__
但未包括 __dict__
作为所定义的空位之一的类(因为这样的类根本没有提供 __dict__
属性)。
如果可变的映射不可用或者如果想要节省空间的键共享,可以通过在 cache() 之上堆叠一个 property() 来实现类似 cached_property() 的效果:
from functools import cache
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@property
@cache
def stdev(self):
return statistics.stdev(self._data)
接下来说一下 @property 和 @cached_property 的不同。看以下案例:
# 使用 @property
# A sample class
class Sample():
def __init__(self):
self.result = 50
@property
# 方法:将结果值增加50
def increase(self):
self.result = self.result + 50
return self.result
# obj 是 Sample 类的一个实例
obj = Sample()
print(obj.increase) # 100
print(obj.increase) # 150
print(obj.increase) # 200
相同的代码,但现在我们使用 @cached_property 替换 @property:
# 用 @cached_property
from functools import cached_property
# A sample class
class Sample():
def __init__(self):
self.result = 50
@cached_property
# 方法:将结果值增加50
def increase(self):
self.result = self.result + 50
return self.result
# obj 是 Sample 类的一个实例
obj = Sample()
print(obj.increase) # 100
print(obj.increase) # 100
print(obj.increase) # 100
可以清楚地看到,使用 @property,每次调用 increase() 方法时都会计算结果的值,这就是为什么它的值会增加 50。但是使用 @cached_property,结果的值只计算一次,然后存储在缓存中,这样每次调用 increase() 方法时都可以访问它,而无需重新计算结果的值,这就是为什么每次输出显示 100。
但是它如何减少执行时间并使程序更快呢?
考虑下面的例子:
# 不使用 @cached_property
# A sample class
class Sample():
def __init__(self, lst):
self.long_list = lst
# 求给定整数值列表之和的方法
def find_sum(self):
return (sum(self.long_list))
# obj 是示例类的一个实例
# 列表可以更长,这只是一个示例
obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(obj.find_sum()) # 55
print(obj.find_sum()) # 55
print(obj.find_sum()) # 55
这里,假设我们传递一个很长的值列表,那么每次调用 find_sum() 方法时都会计算给定列表的总和,从而花费大量的执行时间,我们的程序最终会变慢。我们想要的是,由于列表在创建实例后不会更改,因此如果在调用方法并希望访问该总和时,列表的总和只计算一次,而不是每次计算一次那就太好了。这可以通过使用 @cached_property 来实现。
# 使用 @cached_property
from functools import cached_property
# A sample class
class Sample():
def __init__(self, lst):
self.long_list = lst
# 求给定整数值列表之和的方法
@cached_property
def find_sum(self):
return (sum(self.long_list))
# obj 是示例类的一个实例
obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(obj.find_sum) # 55
print(obj.find_sum) # 55
print(obj.find_sum) # 55
虽然输出相同,但它大大减少了执行时间。在创建实例时传递的列表的总和只计算一次并存储在缓存中,此后,每次调用 find_sum() 方法时,它都使用相同的、已经计算的总和值,并将我们从昂贵的总和重新计算中节省下来。因此,它减少了执行时间,使我们的程序更快。
更新时间:2022-01-18 23:25:00 标签:python 缓存 属性 装饰器