Python functools.lru_cache 实现高速缓存及其原理 源码解析

本文深入探讨了Python的functools.lru_cache装饰器,用于实现函数结果的高速缓存。讲解了其线程安全的特性,以及如何借助闭包和LRU(最近最少使用)算法来更新缓存。内容包括缓存原理、源码分析、功能测试,以及如何通过cache_info()和cache_clear()方法进行信息获取和缓存清理。
摘要由CSDN通过智能技术生成

原理

lru_cache的实现,依赖于Python的闭包,以及LRU算法。另外,这个缓存方式是线程安全的,其生命周期,开始于进程创立后的被装饰函数的的第一次运行,直到进程结束

  1. 借助Python的闭包,实现函数结果的高速缓存
  2. 借助LRU算法(最近最少使用),实现函数结果的高速缓存更新。
  3. 如果,maxsize=None,则将禁用LRU功能,并且缓存可以无限增长。
  4. 如果将typed设置为true,则将分别缓存不同类型的函数参数。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用
  5. 由于使用字典来缓存结果,因此函数的位置和关键字参数必须是可哈希的。
  6. 可以将不同的参数模式视为具有单独的缓存项的不同调用。例如,f(a=1,b=2)f(b=2,a=1) 的关键字参数顺序不同,并且可能具有两个单独的缓存条目。
  7. 借助func.cache_info()方法,查看缓存信息
  8. 借助func.cache_clear()方法,清除缓存信息

讲解中,注释写的比较明白,如想进一步了解,可以仔细阅读

原理测试

功能

下面这个测试函数,只会在第一次运行的时候,停顿1s。其它次运行,不会停顿

import time
from functools import lru_cache


@lru_cache
def sleep(x, y):
    time.sleep(1)
    print('I woke up')
    return x * 10, y * 10

sleep(1, 2)
sleep(1, 2)
缓存原理测试

可以仔细观察,闭包参数的变化,执行函数前后,变化的参数前五个参数(下表中的最上面的5个参数,参数含义参考下文函数讲解)。由此可见,这个装饰器的缓存位置确实是利用闭包来实现

closure_pre = sleep.__closure__
for i in closure_pre:
    print(f'| {i} | {i.cell_contents} |')

sleep(1, 2)
closure = sleep.__closure__
for i in closure:
    print(f' {i} | {i.cell_contents} |')
print('| 第一次运行前 | 闭包参数 | 第一次运行后 | 闭包参数 |')
print('|--|--|--|--|')
参数名 第一次运行前 闭包参数 第一次运行后 闭包参数
cache <cell at 0x000001570D814B80: dict object at 0x000001570D554440> {} <cell at 0x000001570D814B80: dict object at 0x000001570D554440> {[1, 2]: [[[…], […], None, None], [[…], […], None, None], [1, 2], (10, 20)]}
full <cell at 0x000001570D814C10: bool object at 0x00007FF8232CD770> False <cell at 0x000001570D814C10: bool object at 0x00007FF8232CD770> False
misses <cell at 0x000001570D814C40: int object at 0x00007FF823311680> 0 <cell at 0x000001570D814C40: int object at 0x00007FF823311680> s0
hits <cell at 0x000001570D814D00: int object at 0x00007FF823311680> 0 <cell at 0x000001570D814D00: int object at 0x00007FF8233116A0> 1
root <cell at 0x000001570D814D30: list object at 0x000001570D7F4C80> [[…], […], None, None] <cell at 0x000001570D814D30: list object at 0x000001570D7F4C80> [[[…], […], [1, 2], (10, 20)], [[…], […], [1, 2], (10, 20)], None, None]
原函数 <cell at 0x000001570D814DF0: function object at 0x000001570D5A8940> <function sleep at 0x000001570D5A8940> <cell at 0x000001570D814DF0: function object at 0x000001570D5A8940> <function sleep at 0x000001570D5A8940>
PREV <cell at 0x000001570D644250: int object at 0x00007FF823311680> 0 <cell at 0x000001570D644250: int object at 0x00007FF823311680> 0
NEXT <cell at 0x000001570D6441F0: int object at 0x00007FF8233116A0> 1 <cell at 0x000001570D6441F0: int object at 0x00007FF8233116A0> 1
KEY <cell at 0x000001570D5396A0: int object at 0x00007FF8233116C0> 2 <cell at 0x000001570D5396A0: int object at 0x00007FF8233116C0> 2
RESULT <cell at 0x000001570D8143D0: int object at 0x00007FF8233116E0> 3 <cell at 0x000001570D8143D0: int object at 0x00007FF8233116E0> 3
maxsize <cell at 0x000001570D814CD0: int object at 0x00007FF823312680> 128 <cell at 0x000001570D814CD0: int object at 0x00007FF823312680> 128
typed <cell at 0x000001570D814D90: bool object at 0x00007FF8232CD770> False <cell at 0x000001570D814D90: bool object at 0x00007FF8232CD770> False
cache_len <cell at 0x000001570D814BE0: method-wrapper object at 0x000001570D814E20> <method-wrapper ‘len’ of dict object at 0x000001570D554440> <cell at 0x000001570D814BE0: method-wrapper object at 0x000001570D814E20> <method-wrapper ‘len’ of dict object at 0x000001570D554440>
cache_get <cell at 0x000001570D814BB0: builtin_function_or_method object at 0x000001570D7D3BD0> <built-in method get of dict object at 0x000001570D554440> <cell at 0x000001570D814BB0: builtin_function_or_method object at 0x000001570D7D3BD0> <built-in method get of dict object at 0x000001570D554440>
make_key <cell at 0x000001570D814CA0: function object at 0x000001570D81C310> <function _make_key at 0x000001570D81C310> <cell at 0x000001570D814CA0: function object at 0x000001570D81C310> <function _make_key at 0x000001570D81C310>
lock <cell at 0x000001570D814C70: _thread.RLock object at 0x000001570D814E40> <unlocked _thread.RLock object owner=0 count=0 at 0x000001570D814E40> <cell at 0x000001570D814C70: _thread.RLock object at 0x000001570D814E40> <unlocked _thread.RLock object owner=0 count=0 at 0x000001570D814E40>
LRU算法

这里主要是借助循环双链表实现的,参考下图,理解LRU实现过程。

初始化
初始化

添加第一个缓存

第一个缓存

添加第二个缓存
添加第二个缓存

重新运行第一次调用
重新运行第一次调用

缓存已经到了最大值,最旧的缓存替换为新缓存
缓存已经到了最大值,最旧的缓存替换为新缓存

图解绘图来源

Python Tutor,交互式网站,可以查看变量的引用动画

绘图代码,一次运行下面代码,观察变量变化

# 初始化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值