python自带缓存lru_cache用法及扩展的使用
admin
2023-05-01 07:11:58
目录

            本篇博客将结合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()装饰器的顺序不同, 就导致了程序是否报错, 其中主要涉及到两点:

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

            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缓存装饰器提供的功能有:

            ​ 从列出的功能可知,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用法及扩展的使用的文章就介绍到这了,更多相关python自带缓存lru_cache内容请搜索趣讯吧以前的文章或继续浏览下面的相关文章希望大家以后多多支持趣讯吧!

            相关内容

            热门资讯

            苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
            北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
            世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
            长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
            应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
            猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
            猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
            阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
            脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
            demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
            苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
            北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
            应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
            长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
            世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
            阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
            脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
            demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
            北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...