Python中缓存lru_cache的基本介绍和讲解
作者:曲鸟 发布时间:2021-09-30 08:23:08
一、前言
我们经常谈论的缓存一词,更多的类似于将硬盘中的数据存放到内存中以至于提高读取速度,比如常说的redis,就经常用来做数据的缓存。
Python的缓存(lru_cache)是一种装饰在被执行的函数上,将其执行的结果缓存起来,当下次请求的时候,如果请求该函数的传参未变则直接返回缓存起来的结果而不再执行函数的一种缓存装饰器。
那它和redis的区别在哪?有什么优势?怎么使用? 下面为你讲解
二、举例说明
1.现在我们先不使用缓存来写一个求两数之和的函数,并调用执行它两次:
def test(a, b):
print('开始计算a+b的值...')
return a + b
print('1+2等于:', test(1, 2))
print('1+2等于:', test(1, 2))
执行结果
开始计算a+b的值...
1+2等于: 3
开始计算a+b的值...
1+2等于: 3
可以看到test被执行了两次,现在我们加上缓存再进行执行:
from functools import lru_cache
@lru_cache
def test(a, b):
print('开始计算a+b的值...')
return a + b
print(test(1, 2))
print(test(1, 2))
执行结果
开始计算a+b的值...
1+2等于: 3
1+2等于: 3
可以看到test函数只被执行了一次,第二次的调用直接输出了结果,使用了缓存起来的值。
2.当我们使用递归求斐波拉契数列 (斐波那契数列指的是这样一个数列:0,1,1,2,3,5,8,它从第3项开始,每一项都等于前两项之和) 的时候,缓存对性能的提升就尤其明显了:
不使用缓存求第40项的斐波拉契数列
import datetime
def fibonacci(num):
# 不使用缓存时,会重复执行函数
return num if num < 2 else fibonacci(num - 1) + fibonacci(num - 2)
start = datetime.datetime.now()
print(fibonacci(40))
end = datetime.datetime.now()
print('执行时间', end - start)
执行时间
执行时间 0:00:29.004424
使用缓存求第40项的斐波拉契数列:
import datetime
def fibonacci(num):
# 不使用缓存时,会重复执行函数
return num if num < 2 else fibonacci(num - 1) + fibonacci(num - 2)
start = datetime.datetime.now()
print(fibonacci(40))
end = datetime.datetime.now()
print('执行时间', end - start)
执行时间
执行时间 0:00:00
两个差距是非常明显的,因为不使用缓存时,相当于要重复执行了很多的函数,而使用了lru_cache则把之前执行的函数结果已经缓存了起来,就不需要再次执行了。
三、lru_cache 用法
1.参数详解
查看lru_cache源码会发现它可以传递两个参数:maxsize、typed:
def lru_cache(maxsize=128, typed=False):
"""Least-recently-used cache decorator.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
...
"""
1) maxsize
代表被lru_cache装饰的方法最大可缓存的结果数量 (被装饰方法传参不同一样,则结果不一样;如果传参一样则为同一个结果), 如果不指定传参则默认值为128,表示最多缓存128个返回结果,当达到了128个时,有新的结果要保存时,则会删除最旧的那个结果。如果maxsize传入为None则表示可以缓存无限个结果;
2)typed
默认为false,代表不区分数据类型,如果设置为True,则会区分传参类型进行缓存,官方是这样描述的:
如果typed为True,则将分别缓存不同类型的参数,
例如,f(3.0)和f(3)将被视为具有明显的结果。
但在python3.9.8版本下进行测试,typed为false时,按照官方的测试方法测试得到的还是会被当成不同的结果处理,这个时候typed为false还是为true都会区别缓存,这与官方文档的描述存在差异:
from functools import lru_cache
@lru_cache
def test(a):
print('函数被调用了...')
return a
print(test(1.0))
print(test(1))
执行结果
函数被调用了...
1.0
函数被调用了...
但如果是多参数的情况下,则会被当成一个结果:
from functools import lru_cache
@lru_cache
def test(a, b):
print('函数被调用了...')
return a , b
print(test(1.0, 2.0))
print(test(1, 2))
执行结果
函数被调用了...
(1.0, 2.0)
(1.0, 2.0)
这个时候设置typed为true时,则会区别缓存:
from functools import lru_cache
@lru_cache(typed=True)
def test(a, b):
print('函数被调用了...')
return a , b
print(test(1.0, 2.0))
print(test(1, 2))
执行结果
函数被调用了...
(1.0, 2.0)
函数被调用了...
(1, 2)
当传参个数大于1时,才符合官方的说法,不清楚是不是官方举例有误
2. lru_cache不支持可变参数
当传递的参数是dict、list等的可变参数时,lru_cache是不支持的,会报错:
from functools import lru_cache
@lru_cache
def test(a):
print('函数被执行了...')
return a
print(test({'a':1}))
报错结果
TypeError: unhashable type: 'dict'
四、lru_cache 与redis的区别
缓存 | 缓存位置 | 是否支持可变参数 | 是否支持分布式 | 是否支持过期时间设置 | 支持的数据结构 | 需单独安装 |
---|---|---|---|---|---|---|
redis | 缓存在redis管理的内存中 | 是 | 是 | 是 | 支持5种数据结构 | 是 |
lru_cache | 缓存在应用进程的内存中,应用被关闭则被清空 | 否 | 否 | 否 | 字典(参数为:key,结果为:value) | 否 |
五、总结
经过上面的分析,lru_cache 功能相对于redis来说要简单许多,但使用起来更加方便,适用于小型的单体应用。如果涉及的缓存的数据种类比较多并且想更好的管理缓存、或者需要缓存数据有过期时间(类似登录验证的token)等,使用redis是优于lru_cache的。
来源:https://blog.csdn.net/momoda118/article/details/120726050
猜你喜欢
- 背景:由于最近公司项目好像有点受不住并发压力了,优化迫在眉睫。由于当前系统是单数据库系统原因,能优化的地方也尽力优化了但是数据库瓶颈还是严重
- 简单的一个例子,是以前用Dephi写的,前不久刚实现了一个在Python中使用Delphi控件来编写界面程序,于是趁热写一个类似的的查询方案
- 在学习了一点 Python 基础之后,我们可以做一个罚点球的小游戏,大概流程是这样:每一轮,你先输入一个方向射门,然后电脑随机判断一个方向扑
- 网站改版,如何改?如果只是设计、功能和栏目等的稍微变动,这些很简单,从技术 上说并没有多少难度。只是对于网站本身的发展来说,没有多大的作用,
- import numpy as npimport sysdef conv_(img, conv_filter): filter_
- 基于邮件通知的服务监控和告警系统主要功能点:配置专用日志格式记录耗时日志格式:'simple':{'format&#
- opencv 进行任意形状目标识别,供大家参考,具体内容如下工作中有一次需要在简单的图上进行目标识别,目标的形状不固定,并且存在一定程度上的
- 单例模式的实现方式将类实例绑定到类变量上class Singleton(object): _instance = Nonedef
- 一、享元模式享元,可理解为 Python 中的元类、最小粒度的类,系统中存在大量的相似对象时,可以选择享元模式提高资源利用率。享元具有两种状
- 接下来就是数据结构的第一部分,栈。栈是一种遵从后进先出原则(LIFO,全称为Last In First Out)的有序集合。栈顶永远是最新的
- 目录Step 1: 生成spec文件Step 2: 使用pyinstaller 构建可执行文件虽然django项目我们一般通过部署服务器进行
- 因为写js经常需要用到访问样式,我们常用的做法是通过 DOM.style.XXX来读写样式信息的。可是DOM.style这种写法只能访问&l
- 以下是不正确的用法: var exp = undefined; if (exp == undefined) { alert("un
- __getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么
- vue在做大型项目时,会用到多状态管理,vuex允许我们将store分割成多个模块,每个模块内都有自己的state、mutation、act
- 本文实例讲述了Python画图的基本方法。分享给大家供大家参考,具体如下:Python:使用matplotlib绘制图表python绘制图表
- 一、登录以信号与系统课程为例,直接输入网址则出现登录界面:由于学号登录需要验证码,因此选择电话登录:直接在开发者工具中找到手机号输入框、密码
- 本文实例讲述了Python实现将HTML转换成doc格式文件的方法。分享给大家供大家参考,具体如下:网页上的一些文章,因为有格式的原因,它们
- class pygame.Rect Rect对象的属性:Rect 表示的区域必须位于一个 Surface 对象之上,比如游戏的主窗口(scr
- scrollHeight: 获取对象的滚动高度。 scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 sc