​ 本篇博客将结合python官方文档和源码详细讲述lru_cache缓存方法是怎么实现, 它与redis缓存的区别是什么, 在使用时碰上functiontools.wrap装饰器时会发生怎样的变化,以及了解它给我们提供了哪些功能然后在其基础上实现我们自制的缓存方法my_cache。


1. lru_cache的使用

1.1 参数详解

​ 以下是lru_cache方法的实现,我们看出可供我们传入的参数有2个maxsize和typed,如果不传则maxsize的默认值为128,typed的默认值为False。其中maxsize参数表示是的被装饰的方法最大可缓存结果数量, 如果是默认值128则表示被装饰方法最多可缓存128个返回结果,如果maxsize传入为None则表示可以缓存无限个结果,你可能会疑惑被装饰方法的n个结果是怎么来的,打个比方被装饰的方法为def add(a, b):当函数被lru_cache装饰时,我们调用add(1, 2)和add(3, 4)将会缓存不同的结果。如果 typed 设置为true,不同类型的函数参数将被分别缓存。例如, f(3)f(3.0) 将被视为不同而分别缓存。

def lru_cache(maxsize=128, typed=False):
if isinstance(maxsize, int):
if maxsize < 0:
maxsize = 0
elif maxsize is not None:
raise TypeError('Expected maxsize to be an integer or None') def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
return update_wrapper(wrapper, user_function) return decorating_function

1.2 基本用法

​ 在我们编写接口时可能需要缓存一些变动不大的数据如配置信息,我们可能编写如下接口:

@api.route("/user/info", methods=["GET"])
@functools.lru_cache()
@login_require
def get_userinfo_list():
userinfos = UserInfo.query.all()
userinfo_list = [user.to_dict() for user in userinfos]
return jsonify(userinfo_list)

​ 我们缓存了从数据库查询的用户信息,下次再调用这个接口时将直接返回用户信息列表而不需要重新执行一遍数据库查询逻辑,可以有效较少IO次数,加快接口反应速度。

1.3 进阶用法

​ 还是以上面的例子,如果发生用户的删除或者新增时,我们再请求用户接口时仍然返回的是缓存中的数据,这样返回的信息就和我们数据库中的数据就会存在差异,所以当发生用户新增或者删除时,我们需要清除原先的缓存,然后再请求用户接口时可以重新加载缓存。

@api.route("/user/info", methods=["POST"])
@functools.lru_cache()
@login_require
def add_user():
user = UserInfo(name="李四")
db.session.add(user)
db.session.commit() # 清除get_userinfo_list中的缓存
get_userinfo_list = current_app.view_functions["api.get_machine_list"]
cache_info = get_userinfo_list.cache_info()
# cache_info 具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize
# 如果缓存数量大于0则清除缓存
if cache_info[3] > 0:
get_userinfo_list.cache_clear()
return jsonify("新增用户成功")

在上面这个用法中我们,如果我们把lru_cache装饰器和login_require装饰器调换位置时,上述的写法将会报错,这是因为login_require装饰器中用了functiontools.wrap模块进行装饰导致的,具原因我们在下节解释, 如果想不报错得修改成如下写法。

@api.route("/user/info", methods=["POST"])
@login_require
@functools.lru_cache()
def add_user():
user = UserInfo(name="李四")
db.session.add(user)
db.session.commit() # 清除get_userinfo_list中的缓存
get_userinfo_list = current_app.view_functions["api.get_machine_list"]
cache_info = get_userinfo_list.__wrapped__.cache_info()
# cache_info 具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize
# 如果缓存数量大于0则清除缓存
if cache_info[3] > 0:
get_userinfo_list.__wrapped__.cache_clear()
return jsonify("新增用户成功")

2. functiontools.wrap装饰器对lru_cache的影响

​ 在上节我们看到,因为@login_require和@functools.lru_cache()装饰器的顺序不同, 就导致了程序是否报错, 其中主要涉及到两点:

  • login_require装饰器中是否用了@functiontools.wrap()装饰器
  • @login_require和@functools.lru_cache()装饰器的执行顺序问题

当我们了解完这两点后就可以理解上述写法了。

2.1 多个装饰器装饰同一函数时的执行顺序

​ 这里从其他地方盗了一段代码来解释一下,如下:

def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args,**kwargs):
print('Get in inner_a')
res = func(*args,**kwargs)
return res
return inner_a def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args,**kwargs):
print('Get in inner_b')
res = func(*args,**kwargs)
return res
return inner_b @decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2 f(1)

输出结果如下:

'Get in decorator_a'
'Get in decorator_b'
'Get in inner_b'
'Get in inner_a'
'Get in f'

是不是很像django中的中间件的执行顺序,其实原理都差不多。

2.2 functiontools.wrap原理

引用其他博主的描述:

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。

补充:为了访问原函数此函数会设置一个__wrapped__属性指向原函数, 这样就可以解释上面1.3节中我们的写法了。

2.3 使用wrap装饰器前后的变化

未完待续。。。。。。。。。

3. 自制简易的my_cache

3.1 lru_cache提供的功能

lru_cache缓存装饰器提供的功能有:

  • 缓存被装饰对象的结果(基础功能)
  • 获取缓存信息
  • 清除缓存内容
  • 根据参数变化缓存不同的结果
  • LRU算法当缓存数量大于设置的maxsize时清除最不常使用的缓存结果

​ 从列出的功能可知,python自带的lru_cache缓存方法可以满足我们日常工作中大部分需求, 可是它不包含一个重要的特性就是,超时自动删除缓存结果,所以在我们自制的my_cache中我们将实现缓存的超时过期功能。

3.2 cache的核心部件

  • 在作用域内存在一个相对全局的字典变量cache={}

  • 在作用域内设置相对全局的变量包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize和 当前缓存大小 currsize

  • 第二点中的缓存信息中增加缓存加入时间和缓存有效时间

    3.3 my_cache的实现

​ 待实现。。。。。。。。。。。。

4. lru_cache缓存和redis缓存的区别

比较类型 lru_cache redis
缓存类型 缓存在app进程内存中 缓存在redis管理的内存中
分布式 只缓存在单个app进程中 可做分布式缓存
数据类型 hash 参数作为key,返回结果为value 有5种类型的数据结构
适用场景 比较小型的系统、单体应用 常用的缓存解决方案
功能 缓存功能但是缺少过期时间控制,但是使用上更加便捷 具备缓存需要的各种要素

5. 总结

​ 综上所述,python自带的缓存功能使用于稍微小型的单体应用。优点是可以很方便的根据传入不同的参数缓存对应的结果, 并且可以有效控制缓存的结果数量,在超过设置数量时根据LRU算法淘汰命中次数最少的缓存结果。缺点是没有办法对缓存过期时间进行设置。

python自带缓存lru_cache用法及扩展(详细)的更多相关文章

  1. Python @函数装饰器及用法(超级详细)

    函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn() # 执行传入的fn参 ...

  2. Python正则式的基本用法

    Python正则式的基本用法 1.1基本规则 1.2重复 1.2.1最小匹配与精确匹配 1.3前向界定与后向界定 1.4组的基本知识 2.re模块的基本函数 2.1使用compile加速 2.2 ma ...

  3. python笔记之常用模块用法分析

    python笔记之常用模块用法分析 内置模块(不用import就可以直接使用) 常用内置函数 help(obj) 在线帮助, obj可是任何类型 callable(obj) 查看一个obj是不是可以像 ...

  4. python中ConfigParse模块的用法

    ConfigParser 是Python自带的模块, 用来读写配置文件, 用法及其简单. 配置文件的格式是: [...]包含的叫section section 下有option=value这样的键值 ...

  5. django之缓存的用法, 文件形式与 redis的基本使用

    django的缓存的用法讲解 1. django缓存: 缓存的机制出现主要是缓解了数据库的压力而存在的 2. 动态网站中,用户的请求都会去数据库中进行相应的操作,缓存的出现是提高了网站的并发量 3. ...

  6. Python numpy中矩阵的用法总结

    关于Python Numpy库基础知识请参考博文:https://www.cnblogs.com/wj-1314/p/9722794.html Python矩阵的基本用法 mat()函数将目标数据的类 ...

  7. python集合与字典的用法

    python集合与字典的用法 集合: 1.增加  add 2.删除   •del 删除集合 •discard(常用)删除集合中的元素  #删除一个不存在的元素不会报错 •remove 删除一个不存在的 ...

  8. Python中int()函数的用法浅析

      int()是Python的一个内部函数 Python系统帮助里面是这么说的 >>> help(int)  Help on class int in module __builti ...

  9. python骚操作---Print函数用法

    ---恢复内容开始--- python骚操作---Print函数用法 在 Python 中,print 可以打印所有变量数据,包括自定义类型. 在 3.x 中是个内置函数,并且拥有更丰富的功能. 参数 ...

随机推荐

  1. Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype

    条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...

  2. docker生产——容器通信

    简介 在接触docker的第一天起,大家应该就知道:docker容器使用沙箱机制,相互之间没有接口,一般情况下内部访问通过IP+端口.本地容器默认分配的IP极易发生变化,所以靠IP+端口访问的方式缺失 ...

  3. com.aliyun.oss.ClientException: Connection error due to: Connection pool shut down

    com.aliyun.oss.ClientException: Connection error due to: Connection pool shut down[ErrorCode]: Unkno ...

  4. Python_爬虫_urllib解析库

    简介:提取网页保存到txt文件中 + 解析txt文件内容,取出内容 from urllib import request import re.json url="http://www.163 ...

  5. [LeetCode题解]109. 有序链表转换二叉搜索树 | 快慢指针 + 递归

    题目描述 给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定的有序链表: ...

  6. Android light系统分析

    光线系统包括:背光,闪光,led指示灯   一.内核层     Led-class.c (kernel-3.10\drivers\leds) 这个文件给HAL层提供接口     led_brightn ...

  7. Vegas教程分享,制作古装墨迹笔刷开场效果

    许多酷炫的古装大片,片头曲介绍人物的时候,都有一种墨迹笔刷的开场效果,那么这个特效如何利用Vegas去做呢? 1.导入素材文件 首先呢,导入相关文件素材到视频制作软件Vegas中,点击页面上方如图1箭 ...

  8. jQuery 第二章 实例方法 DOM操作选择元素相关方法

    进一步选择元素相关方法:  .get() .eq() .find() .filter() .not() .is() .has() .add()集中操作  .end()回退操作 .get() $(&qu ...

  9. spring中的事务传播机制

    1.事务的实现思想 在spring中要想某个方法具有事务,只要在方法前加一个@Transactional注解.然后spring就会利用aop思想,在这个方法执行前开启事务, 在方法执行后选择提交事务或 ...

  10. 《图解TCP/IP》第四章

    <图解TCP/IP>第四章 4.1 IP 即网际协议 4.1.1 IP(IPv4.IPv6)相当于OSI参考模型中的第3层-网络层 4.1.2 数据链路层和网络层的关系: 数据链路层的主要 ...