本篇博客将结合python官方文档和源码详细讲述lru_cache缓存方法是怎么实现, 它与redis缓存的区别是什么, 在使用时碰上functiontools.wrap装饰器时会发生怎样的变化,以及了解它给咱们提供了哪些功能而后在其基础上实现咱们自制的缓存方法my_cache。html
如下是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)
将被视为不一样而分别缓存。python
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
在咱们编写接口时可能须要缓存一些变更不大的数据如配置信息,咱们可能编写以下接口:redis
@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次数,加快接口反应速度。算法
仍是以上面的例子,若是发生用户的删除或者新增时,咱们再请求用户接口时仍然返回的是缓存中的数据,这样返回的信息就和咱们数据库中的数据就会存在差别,因此当发生用户新增或者删除时,咱们须要清除原先的缓存,而后再请求用户接口时能够从新加载缓存。数据库
@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模块进行装饰致使的,具缘由咱们在下节解释, 若是想不报错得修改为以下写法。django
@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("新增用户成功")
在上节咱们看到,由于@login_require和@functools.lru_cache()装饰器的顺序不一样, 就致使了程序是否报错, 其中主要涉及到两点:json
- login_require装饰器中是否用了@functiontools.wrap()装饰器
- @login_require和@functools.lru_cache()装饰器的执行顺序问题
当咱们了解完这两点后就能够理解上述写法了。api
这里从其余地方盗了一段代码来解释一下,以下:缓存
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)
输出结果以下:session
'Get in decorator_a' 'Get in decorator_b' 'Get in inner_b' 'Get in inner_a' 'Get in f'
是否是很像django中的中间件的执行顺序,其实原理都差很少。
引用其余博主的描述:
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已是另一个函数了(函数名等函数属性会发生改变),为了避免影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的反作用。写一个decorator的时候,最好在实现以前加上functools的wrap,它能保留原有函数的名称和docstring。
补充:为了访问原函数此函数会设置一个
__wrapped__
属性指向原函数, 这样就能够解释上面1.3节中咱们的写法了。
未完待续。。。。。。。。。
lru_cache缓存装饰器提供的功能有:
- 缓存被装饰对象的结果(基础功能)
- 获取缓存信息
- 清除缓存内容
- 根据参数变化缓存不一样的结果
- LRU算法当缓存数量大于设置的maxsize时清除最不常使用的缓存结果
从列出的功能可知,python自带的lru_cache缓存方法能够知足咱们平常工做中大部分需求, 但是它不包含一个重要的特性就是,超时自动删除缓存结果,因此在咱们自制的my_cache中咱们将实现缓存的超时过时功能。
在做用域内存在一个相对全局的字典变量cache={}
在做用域内设置相对全局的变量包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize和 当前缓存大小 currsize
第二点中的缓存信息中增长缓存加入时间和缓存有效时间
待实现。。。。。。。。。。。。
比较类型 | lru_cache | redis |
---|---|---|
缓存类型 | 缓存在app进程内存中 | 缓存在redis管理的内存中 |
分布式 | 只缓存在单个app进程中 | 可作分布式缓存 |
数据类型 | hash 参数做为key,返回结果为value | 有5种类型的数据结构 |
适用场景 | 比较小型的系统、单体应用 | 经常使用的缓存解决方案 |
功能 | 缓存功能可是缺乏过时时间控制,可是使用上更加便捷 | 具有缓存须要的各类要素 |
综上所述,python自带的缓存功能使用于稍微小型的单体应用。优势是能够很方便的根据传入不一样的参数缓存对应的结果, 而且能够有效控制缓存的结果数量,在超过设置数量时根据LRU算法淘汰命中次数最少的缓存结果。缺点是没有办法对缓存过时时间进行设置。