← 返回题目列表

Python 的 functools.lru_cache 有什么用?使用时要注意什么?

高频 中等 第 1 / 27 题 更新于 2026/07/27
lru_cache缓存functools可哈希

简化版

functools.lru_cache 是函数结果缓存装饰器,会根据函数参数缓存返回值,重复调用相同参数时直接返回缓存结果。它适合纯函数和计算昂贵的函数,但参数必须可哈希,并且要注意缓存大小、内存占用和数据过期问题。

详细版

示例:

from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

没有缓存时,递归斐波那契会重复计算大量子问题;加上 lru_cache 后,相同 n 的结果只计算一次。

注意点:

  • 函数参数必须是可哈希对象,比如整数、字符串、元组。
  • 不适合缓存依赖外部状态、时间、数据库实时数据的函数,除非你能接受旧数据。
  • maxsize 控制最多缓存多少组结果,None 表示不限制大小。
  • 可以用 cache_info() 查看命中情况,用 cache_clear() 清空缓存。
print(fib.cache_info())
fib.cache_clear()

面试回答要同时讲收益和边界:它能减少重复计算,但不是分布式缓存,也没有自动过期时间。

完整版教学

一、lru_cache 是什么

lru_cache 是标准库 functools 提供的装饰器。

from functools import lru_cache

@lru_cache(maxsize=128)
def query(key):
    ...

当你第一次调用:

query("a")

函数正常执行,并把参数 "a" 对应的结果存入缓存。下一次再调用 query("a"),如果缓存还在,就直接返回上次结果。

LRU 是 Least Recently Used,表示缓存满了以后,优先淘汰最近最少使用的条目。

二、为什么递归里很常用

看斐波那契:

def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

计算 fib(5) 会重复计算 fib(3)fib(2) 等子问题。

加缓存:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

每个 n 只计算一次,复杂度会从指数级重复递归明显降低。

三、缓存键如何生成

lru_cache 会根据函数参数构造缓存键,所以参数必须可哈希。

可以:

@lru_cache
def f(x: int, name: str):
    ...

不可以:

@lru_cache
def f(items: list[int]):
    ...

列表不可哈希,会报 TypeError

如果确实要缓存列表含义,可以把列表转成元组:

@lru_cache
def f(items: tuple[int, ...]):
    ...

f(tuple([1, 2, 3]))

四、哪些函数适合缓存

适合缓存的函数通常有这些特征:

  • 相同输入总是得到相同输出;
  • 计算成本较高;
  • 参数空间不会无限膨胀;
  • 结果可以在一段时间内复用;
  • 没有写数据库、发消息这类副作用。

例如:

  • 递归动态规划;
  • 解析配置;
  • 编译模板;
  • 本地纯计算。

不适合直接缓存:

  • 依赖当前时间的函数;
  • 查询实时数据库的函数;
  • 依赖用户权限变化的函数;
  • 有副作用的函数。

五、maxsize 怎么选

@lru_cache(maxsize=128)

表示最多保留 128 组调用结果。缓存满了会淘汰最近最少使用的结果。

@lru_cache(maxsize=None)

表示不限制缓存大小。它可能带来内存增长风险,除非你确定参数种类有限。

工程里 maxsize 不应该随手写很大,要根据参数分布和内存预算设置。

六、缓存观测和清理

lru_cache 装饰后的函数有辅助方法:

fib.cache_info()

可以看到命中次数、未命中次数、最大容量、当前大小。

fib.cache_clear()

可以清空缓存。

在测试里,如果缓存影响用例隔离,记得清理缓存:

def test_fib():
    fib.cache_clear()
    assert fib(10) == 55

七、常见误区与追问

记忆钩子:lru_cache 缓的是“同样入参对应的返回值”。它适合纯函数和重复计算,不适合把变化的外部世界偷偷缓存起来。

  • 误区:加了 lru_cache 函数一定更快。 第一次调用仍要计算,缓存还会带来哈希、锁和内存开销;只有重复调用比例高、计算成本明显时才划算。
  • 追问:为什么参数必须可哈希? 缓存需要用函数参数构造 key 放进字典,列表、字典这类可变不可哈希对象不能直接作为缓存键。
  • 误区:缓存结果会自动随数据库或文件变化更新。 lru_cache 不知道外部数据源是否变化,缓存失效要靠 cache_clear()、版本参数或更明确的业务缓存策略。
  • 追问:maxsize=None 有什么风险? 它表示不限制缓存数量,长生命周期服务中可能因参数种类不断增长而占用越来越多内存。
  • 误区:实例方法加 lru_cache 只按业务参数缓存。 实例方法的 self 也参与 key;如果大量实例调用同一方法,缓存可能按实例分裂,甚至延长实例生命周期。
  • 追问:怎么观察缓存是否有效?func.cache_info() 查看 hits、misses、maxsize、currsize;如果 misses 很高,说明缓存命中率低,可能不值得保留。

八、加强记忆

lru_cache 是“按参数缓存函数结果”的装饰器,适合纯计算和重复子问题。它快的前提是参数可哈希、结果可复用、缓存大小可控。面试时要讲清它不是万能缓存:没有自动过期,不适合实时数据和副作用函数。