​ 本篇博客将结合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. JNI-从jvm源码分析Thread.interrupt的系统级别线程打断原理

    前言 在java编程中,我们经常会调用Thread.sleep()方法使得线程停止运行一段时间,而Thread类中也提供了interrupt方法供我们去主动打断一个线程.那么线程挂起和打断的本质究竟是 ...

  2. 安全也挺让人心烦的 ---login shell

    今天查问题时, ssh 登录后台发现 需要输入密码,输入密码后弹出一个二维码, 然后扫码获取秘钥.输入秘钥登陆!!! 真是恶心了一把,找手机都花了不少时间!!!! 那么怎样干掉输入密码这些操作呢??? ...

  3. Android自定控件基础(一)——几何图形绘制

    虽然本人有几年开发经验,但是自定义控件这一块儿,研究的很少,惭愧--用到的时候就是百度查找,复制粘贴.工时紧,总是想的快点完工就好.(都是借口啦,想学总会有时间哒) 作为一个Android开发 要说自 ...

  4. ssh2中的添,删,查,改。

    1.spring封装的HibernateTemplate类的一些操作方法. 2.session提供的根据主键ID进行添.删.查.改的基本方法. 由session得到的hql语句 由session得到的 ...

  5. 关于backfill参数建议

    前言 在做一个比较满的集群的扩容的时候,遇到了一些问题,在这里做下总结,一般来说很难遇到,扩容要趁早,不然出的问题都是稀奇古怪的一些问题 建议 环境一般来说在70%左右就需要考虑扩容了,这个时候的扩容 ...

  6. Tarjan算法求割点

    (声明:以下图片来源于网络) Tarjan算法求出割点个数 首先来了解什么是连通图 在图论中,连通图基于连通的概念.在一个无向图 G 中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称 ...

  7. C#设计模式——代理模式(Proxy Pattern)

    引言 在我们的生活中,经常会遇到需要什么东西,但是自己又不是很方便或者对方不是很方便,则就需要中间的一个代理人去解决.例如代购.在软件开发中,也会遇到这样的问题.有些对象有时候会由于网络或其他的障碍, ...

  8. 如何个性化定制iview中的table样式

    使用renderHeader.render函数 例子: column:[ {}, ..., { title:'较年初占比变化', key:''lastYearChange, renderHeader: ...

  9. sqlilab less28 less28a

    less-28  less-28a 二者相差不大 单引号小括号包裹,黑名单过滤--,#,空格,union空格select(不区分大小写) less-28的黑名单 less-28a的黑名单 %a0,不被 ...

  10. 维吉尼亚密码-攻防世界(shanghai)

    维吉尼亚密码 维吉尼亚密码是使用一系列 凯撒密码 组成密码字母表的加密算法,属于多表密码的一种简单形式. 加密原理 维吉尼亚密码的前身,是我们熟悉的凯撒密码. 凯撒密码的加密方式是依靠一张字母表中的每 ...