Python functools模块中lru_cache的内部工作原理探究
深入理解Python functools.lru_cache的内部实现
这个问题问得很到位!咱们来拆解一下functools.lru_cache的底层实现,把你的两个疑问讲清楚:
1. 它是否像Python其他组件一样使用字典?
是的,但它不是单纯用普通字典——为了实现LRU(最近最少使用)的淘汰策略,lru_cache结合了哈希表(字典)+ 双向链表的组合结构(Python 3.7及以后的版本里,OrderedDict本身就是基于这种结构实现的,早期的lru_cache直接依赖OrderedDict,后来的版本做了更高效的底层优化)。
- 字典的核心作用是快速查找:把函数的输入参数转换成可哈希的键后,能在O(1)时间内找到对应的缓存值,这和普通字典的查找逻辑一致。
- 双向链表则用来维护访问顺序:每次访问一个缓存项,就把它移到链表头部;当缓存达到设定的
maxsize上限时,直接淘汰链表尾部的项(也就是最近最少被使用的那个)。
这里给你一个简化版的核心逻辑示意(不是实际源码,帮你理解核心思路):
# 简化版LRU缓存结构 class SimpleLRUCache: def __init__(self, maxsize): self.cache_map = dict() # 存储参数键到(返回值, 链表节点)的映射 self.access_list = DoublyLinkedList() # 维护缓存项的访问顺序 self.maxsize = maxsize def get(self, key): if key in self.cache_map: value, node = self.cache_map[key] self.access_list.move_to_head(node) # 标记为最近使用 return value return None def put(self, key, value): if key in self.cache_map: # 已存在的缓存项,更新值并移到头部 node = self.cache_map[key][1] self.access_list.move_to_head(node) self.cache_map[key] = (value, node) else: # 新缓存项,先检查是否超容 if len(self.cache_map) >= self.maxsize: # 淘汰最久未使用的项 tail_node = self.access_list.remove_tail() del self.cache_map[tail_node.key] # 添加新项到头部 new_node = self.access_list.add_to_head(key) self.cache_map[key] = (value, new_node)
实际的lru_cache源码会更复杂,比如要处理可哈希参数的校验、关键字参数的标准化(把*args和排序后的**kwargs组合成唯一的哈希键)、线程安全的同步等,但核心结构确实是哈希表+顺序维护结构。
2. 是否仅存储返回值?
当然不是!它存储的是参数键到返回值的映射,但为了实现LRU逻辑和统计功能,还会额外存储不少元数据:
- 首先,它会把函数的输入参数(包括位置参数和关键字参数)转换成一个唯一的可哈希键(比如将
args元组和排序后的kwargs键值对组合成新的元组),这个键是字典的key。 - 对应的value部分,除了函数的返回值,还包含用于维护LRU顺序的信息(比如双向链表节点的引用、访问时间标记等)。
- 另外,
lru_cache还会自动维护缓存的统计数据:比如命中次数、未命中次数、当前缓存大小等,你可以通过cache_info()方法查看这些数据:
from functools import lru_cache @lru_cache(maxsize=10) def add(a, b): return a + b add(1, 2) add(1, 2) print(add.cache_info()) # 输出:CacheInfo(hits=1, misses=1, maxsize=10, currsize=1)
这些统计数据也是lru_cache内部存储的额外信息,不属于函数的返回值,但对缓存的监控和调优非常有用。
内容的提问来源于stack exchange,提问作者innicoder
相关产品推荐
相关产品推荐

