您现在的位置是:首页 > 技术教程 正文

如何使用Python内置缓存装饰器: @lru_cache,@cache 与 @cached_property

admin 阅读: 2024-03-23
后台-插件-广告管理-内容页头部广告(手机)

1. 为什么需要缓存技术?

使用缓存是优化Python程序速度的重要方法之一。如果使用得当,可以大幅减少计算资源的负载,有效加快代码运行速度

Python 的内置库 functools 模块附带了@lru_cache,@cache, @cached_property 装饰器,使用非常简便,不需要安装第3方库,不需要 redis 等数据库保存对象等,通常只需要1行代码,就可以对函数运算结果、类成员方法运算结果进行缓存。

本文将介绍这3种缓存工具的使用步骤及实例。

2. @lru_cache 缓存装饰器的使用

@lru_cache 是最常见的缓存装饰器。lru_cache 是: Last recently used cache 的简写,可以将该函数最近调用的输入参数以及结果进行缓存。如果有新的调用,先检查缓存是否有相同的输入参数,如果存在,则直接返回对应结果。如果是无参函数,第1次调用后,以后每次调用,直接返回缓存结果。

先看1个例子

from functools import lru_cache from math import sin @lru_cache def sin_half(x): return sin(x)/2 print('first call result:',sin_half(60)) print('second call result:',sin_half(60))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上例中,第1次运行函数后,lru_cache会缓存调用参数及返回结果。第2次运行时,lru_cache都会检查输入,发现缓存中存在相同输入参数60,则从缓存中返回结果。如果函数执行的是计算量很重的任务,对于相同输入,可以明显地节省系统资源。

装饰器参数

lru_cache默认不清除缓存内容,因此缓存会无限增长,如果程序是长期运行的服务,可能存在耗尽内存的风险。 因此,必须添加1个maxsize参数:
@lru_cache(maxsize) 的参数maxsize 表示要缓存的最近调用次数.
如 @lru_cache(360) 表示,只缓存最近360次的函数调用结果。

@lru_cache(360) def sin_half(x): return sin(x)/2
  • 1
  • 2
  • 3

缓存操作

查看缓存报告: sin_half.cache_info()
强制清除缓存内容: sin_half.cache_clear()
当然也可以使用 python的 garbage collection 清除缓存。

下面使用1个示例来演示上述内容:

import functools import gc # 主要功能: # 验证 @lru_cache 装饰器,.chche_info() 和 .cache_clear() 方法的使用 # garbage collection 的使用 @functools.lru_cache(maxsize = 300) # Max number of Last recently used cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) fib(30) fib.cache_clear() # Before Clearing print(fib.cache_info()) # After Clearing print(fib.cache_info()) @functools.lru_cache(maxsize = None) def gfg1(): # insert function logic here pass # 再次运行函数 gfg1() fib(30) # garbage collection gc.collect() # All objects collected objects = [i for i in gc.get_objects() if isinstance(i, functools._lru_cache_wrapper)] print(gfg1.cache_info()) # All objects cleared for object in objects: object.cache_clear() print(gfg1.cache_info())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

运行程度,输出为:

CacheInfo(hits=0, misses=0, maxsize=300, currsize=0) CacheInfo(hits=0, misses=0, maxsize=300, currsize=0) CacheInfo(hits=0, misses=1, maxsize=None, currsize=1) CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)
  • 1
  • 2
  • 3
  • 4

3. @cache 缓存装饰器的使用

相比@lru_cache, @cache 装饰器更轻量化,速度更快,且是线程安全,不同线程可以调用同1个函数,缓存值可以共享。

import functools import time @functools.cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) if __name__ == '__main__': start_time = time.time() print(fib(400)) end_time = time.time() execution_time_without_cache = end_time - start_time print("Time taken without cache: {:.8f} seconds".format(execution_time_without_cache)) start_time = time.time() print(fib(400)) end_time = time.time() execution_time_without_cache = end_time - start_time print("Time taken with cache: {:.8f} seconds".format(execution_time_without_cache))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

output:

176023680645013966468226945392411250770384383304492191886725992896575345044216019675 Time taken without cache: 0.00095391 seconds 176023680645013966468226945392411250770384383304492191886725992896575345044216019675 Time taken with cache: 0.00000000 seconds
  • 1
  • 2
  • 3
  • 4

4. @cached_property 缓存装饰器的使用

@cached_property是一个装饰器,它将类的方法转换为属性,其值仅计算一次,然后缓存为普通属性。因此,只要实例持久存在,缓存的结果就可用,我们可以将该方法用作类的属性那样来使用,如

调用: : instance.method 取代旧方式 : instance.method()
  • 1
  • 2

@cached_property是 Python 中 functools 模块的一部分。它类似于 property(),但 @cached_property 带有一个额外的功能,那就是缓存。

但是它如何减少执行时间并使程序更快?请考虑以下示例:

# Without using @cached_property # A sample class class Sample(): def __init__(self, lst): self.long_list = lst # a method to find the sum of the # given long list of integer values def find_sum(self): return (sum(self.long_list)) # obj is an instance of the class sample # the list can be longer, this is just # an example obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print(obj.find_sum()) print(obj.find_sum()) print(obj.find_sum())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

输出为:

55 55 55
  • 1
  • 2
  • 3

在这里,假设如果我们传递1个长列表,每次调用 find_sum()方法时都会计算给定列表的总和,从而花费大量时间运行,程序最终会变慢。我们想要的是,由于列表在创建实例后不会更改,因此,如果列表的总和只计算一次,而不是每次在我们调用方法并希望访问总和时计算,那就太好了。这可以通过使用@cached_property来实现,

# With using @cached_property from functools import cached_property # A sample class class Sample(): def __init__(self, lst): self.long_list = lst # a method to find the sum of the # given long list of integer values @cached_property def find_sum(self): return (sum(self.long_list)) # obj is an instance of the class sample obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print(obj.find_sum) print(obj.find_sum) print(obj.find_sum)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

使用了@cached_property装饰器后,无须重新计算,总是立即返回find_sum值。
如果需要重新计算属性,则删除该属性即可,下次调用会重新计算,在本例 中添加如下语句,

del obj.find_sum obj.long_list = range(20,30) print(obj.find_sum)
  • 1
  • 2
  • 3

运行代码,输出结果为:

55 55 55 245
  • 1
  • 2
  • 3
  • 4

5. 各种缓存方法的适用场景

综上所述,建议如下:
1) 如果程序规模小,或多线程编程时,可使用@cache 装饰器。
2) 如果程度规模大,并且是长时间运行,建议使用@lru_cache 装饰器,使用方法灵活,但要注意控制缓存数量,必要时手工清理。
3) 编写类代码时,如需将某项复杂运算结果像属性那样访问时,使用@cached_property装饰器。

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索