@cache 到底是什么?
@cache 是 Python 3.9 引入的内置装饰器(位于 functools 模块),它的核心作用是把函数的计算结果缓存起来。只要输入参数相同,就直接返回之前算过的结果,而不是重新计算一次。它特别适合那些计算代价高、但输入参数重复出现的场景。

为什么要用 @cache?手动缓存的痛点
我们先看一个不使用 @cache 的经典例子:计算斐波那契数的递归函数。
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(35)) # 运行几秒甚至十几秒才能出结果
这个递归版本的斐波那契效率极低,因为它会反复计算相同的子问题,比如 fib(30) 会被计算无数次。
我们第一时间想到的优化方法是:加个字典手动缓存。
cache_dict = {}
def fib(n):
if n in cache_dict:
return cache_dict[n]
if n <= 1:
result = n
else:
result = fib(n-1) + fib(n-2)
cache_dict[n] = result
return result
print(fib(35)) # 瞬间出结果
这样确实快了很多,但有几个明显的问题:
- 全局变量污染:
cache_dict 是全局的,如果多个函数都想缓存,就得写多个字典,管理起来很混乱。
- 代码侵入性强:每次写新函数都要手动加这一套“查缓存 → 计算 → 存缓存”的逻辑,重复代码很多。
- 不优雅:如果函数参数是列表、字典等可变对象,或者有关键字参数,手动处理会更麻烦。
- 维护麻烦:想加缓存上限、想清空缓存、想统计命中率,都得自己再写一堆代码。
有没有一种方法,能一行代码就让函数自动记住所有调用结果,而且不污染全局、不用改函数内部逻辑、还能处理各种参数类型?
答案就是 @cache(或它的升级版 @lru_cache)。
使用 @cache,一行代码的魔法
我们直接给上面的函数加一行装饰器:
from functools import cache
@cache
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(35)) # 瞬间出结果
print(fib(40)) # 还是瞬间
print(fib(35)) # 直接从缓存返回,0计算
加了 @cache 后,第一次调用 fib(35) 时,它会正常递归计算,同时把每个子问题的结果存起来。第二次调用 fib(35) 或 fib(30) 时,直接从内存缓存中取值,几乎零耗时。整个函数内部代码一行没改!
再看一个更实际的例子:模拟耗时操作。
from functools import cache
import time
@cache
def expensive_calc(n):
print(f"正在计算 expensive_calc({n}) ...")
time.sleep(2) # 模拟2秒的昂贵计算
return n * n
print(expensive_calc(10)) # 打印计算中... 等待2秒 → 100
print(expensive_calc(10)) # 直接返回 100(无打印、无等待)
print(expensive_calc(20)) # 打印计算中... 等待2秒 → 400
你会发现:相同参数的调用只真正执行一次,后面都是秒返回。
@cache 的几种常见写法对比
from functools import cache, lru_cache
# 1. 最简单无限缓存(Python 3.9+)
@cache
def func(a, b):
...
# 2. 带缓存上限的 LRU(最常用)
@lru_cache(maxsize=128) # 默认128,设为None则为无限缓存
def func(a, b):
...
# 3. 带类型提示(推荐写法)
from functools import cache
@cache
def add(a: int, b: int) -> int:
print(f"计算 {a} + {b}")
return a + b
@cache 的运行逻辑彻底拆解
我们用下面这个简单的例子来一步步讲清楚它在底层怎么工作:
from functools import cache
@cache
def add(a, b):
print(f"真正计算 {a} + {b}")
return a + b
print(add(3, 4)) # 真正计算 3 + 4 → 7
print(add(3, 4)) # 直接返回 7(无打印)
print(add(5, 6)) # 真正计算 5 + 6 → 11
当你写 @cache 时,发生了这些事:
- 装饰器在定义时执行:
@cache 是一个装饰器,它把 add 函数包装成了一个带缓存的版本。
- 生成一个缓存字典:内部会创建一个字典(或 LRU 结构),键是参数的唯一化表示,值是计算结果。
- 参数如何变成 key?:将位置参数和关键字参数打包成
(args_tuple, kwargs_frozendict)。所有不可哈希的对象(如 list、dict)作为参数时会报错(TypeError: unhashable type)。这就是为什么 @cache 要求参数必须可哈希(如 int、str、tuple 等)。
- 调用流程:
- 调用
add(3, 4)
- 先把参数转为 key:
((3, 4), {})
- 查缓存字典,命中了吗?
- 命中 → 直接返回缓存结果
- 未命中 → 真正执行原函数
add(3, 4),得到 7
- 把
((3, 4), {}) : 7 存进缓存
- 返回
7
为什么第二次调用不打印“真正计算”?因为第二次查缓存命中了,直接跳过了原函数体。
但需要注意几个关键点:
- 函数参数必须是可哈希的。
@cache 的缓存会一直占用内存(除非使用 @lru_cache 并设置 maxsize)。
- 不适合参数变化极大、调用次数极多的场景(缓存可能会占用过多内存)。
合理地使用 @cache 装饰器,可以让你在编写Python代码时避免大量重复计算,显著提升程序性能,同时保持代码的简洁与优雅。如果你对这类提升开发效率的技巧感兴趣,可以在云栈社区找到更多相关的深度讨论和实践分享。