Python中的lru_cache装饰器

参考:https://www.cnblogs.com/lifei01/p/14105346.html

1. Iru_cache介绍

1.1 lru_cache提供的功能

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

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

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

1.2 cache的核心部件

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

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

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

1.3 应用场景

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

2. lru_cache的使用

2.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) 将被视为不同而分别缓存。

复制代码
 1 def lru_cache(maxsize=128, typed=False):
 2     if isinstance(maxsize, int):
 3         if maxsize < 0:
 4             maxsize = 0
 5     elif maxsize is not None:
 6         raise TypeError('Expected maxsize to be an integer or None')
 7 
 8     def decorating_function(user_function):
 9         wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
10         return update_wrapper(wrapper, user_function)
11 
12     return decorating_function
复制代码

2.2 基本用法

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

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

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

2.3 进阶用法

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

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

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

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

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

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

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

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

复制代码
 1 def decorator_a(func):
 2     print('Get in decorator_a')
 3     def inner_a(*args,**kwargs):
 4         print('Get in inner_a')
 5         res = func(*args,**kwargs)
 6         return res
 7     return inner_a
 8 
 9 def decorator_b(func):
10     print('Get in decorator_b')
11     def inner_b(*args,**kwargs):
12         print('Get in inner_b')
13         res = func(*args,**kwargs)
14         return res
15     return inner_b
16 
17 
18 @decorator_b
19 @decorator_a
20 def f(x):
21     print('Get in f')
22     return x * 2
23 
24 f(1)
复制代码

输出结果如下:

1 'Get in decorator_a'
2 'Get in decorator_b'
3 'Get in inner_b'
4 'Get in inner_a'
5 'Get in f'

3.2 functiontools.wrap原理

引用其他博主的描述:

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

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

4. 注意事项

被lru_cache()装饰的函数不能分辨运行时环境的变化而智能对参数进行缓存,就导致下面的函数运行结果的不同:

复制代码
 1 import functools
 2 
 3 class Solution:
 4 
 5     Member = 10
 6 
 7     def add(self, num1, num2):
 8         self.Member += 1
 9         return num1 + num2 + self.Member
10 
11 s = Solution()
12 print(s.add(3, 4))    # result: 18
13 print(s.add(3, 4))    # result: 19
复制代码

加上装饰器以后:

复制代码
 1 import functools
 2 
 3 class Solution:
 4 
 5     Member = 10
 6 
 7     @functools.lru_cache()
 8     def add(self, num1, num2):
 9         self.Member += 1
10         return num1 + num2 + self.Member
11 
12 s = Solution()
13 print(s.add(3, 4))    # result: 18
14 print(s.add(3, 4))    # result: 18
复制代码

 

posted @   Asp1rant  阅读(614)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2018-10-24 Ros Kinetic 配置 OpenCV2和CV_bridge (Python, C++)
点击右上角即可分享
微信分享提示