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

一、为什么要用 @cache?
我们先不使用 @cache,看看不用它会发生什么。
假设我们要写一个计算斐波那契数的函数:
def fib(n):if n <= 1:return nreturn 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 = nelse:result = fib(n-1) + fib(n-2)cache_dict[n] = resultreturn resultprint(fib(35)) # 瞬间出结果
这样确实快了很多,但有几个明显的问题:
1.全局变量污染:cache_dict 是全局的,如果多个函数都想缓存,就得写多个字典,或者搞得很乱。
2.代码侵入性强:每次写新函数都要手动加这一套“查缓存 → 计算 → 存缓存”的逻辑,重复代码很多。
3.不优雅:如果函数参数是列表、字典等可变对象,或者有关键字参数,手动处理会更麻烦。
4.维护麻烦:想加缓存上限、想清空缓存、想统计命中率,都得自己再写一堆代码。
有没有一种方法,能一行代码就让函数自动记住所有调用结果,
而且不污染全局、不用改函数内部逻辑、还能处理各种参数类型?
答案就是 @cache(或它的升级版 @lru_cache)。
使用 @cache
我们直接给上面的函数加一行装饰器:
from functools import cache@cachedef fib(n):if n <= 1:return nreturn 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 cacheimport time@cachedef expensive_calc(n):print(f"正在计算 expensive_calc({n}) ...")time.sleep(2) # 模拟2秒的昂贵计算return n * nprint(expensive_calc(10)) # 打印计算中... 等待2秒 → 100print(expensive_calc(10)) # 直接返回 100(无打印、无等待)print(expensive_calc(20)) # 打印计算中... 等待2秒 → 400
你会发现:相同参数的调用只真正执行一次,后面都是秒返回。
二、@cache 的几种常见写法对比
from functools import cache, lru_cache# 1. 最简单无限缓存(3.9+)@cachedef func(a, b):...# 2. 带缓存上限的 LRU(最常用)@lru_cache(maxsize=128) # 默认128,设None无限def func(a, b):...# 3. 带类型提示(推荐)from functools import cache@cachedef add(a: int, b: int) -> int:print(f"计算 {a} + {b}")return a + b
三、@cache 的运行逻辑彻底拆解
我们用这个例子来一步步讲清楚它在底层怎么工作:
from functools import cache@cachedef add(a, b):print(f"真正计算 {a} + {b}")return a + bprint(add(3, 4)) # 真正计算 3 + 4 → 7print(add(3, 4)) # 直接返回 7(无打印)print(add(5, 6)) # 真正计算 5 + 6 → 11
当你写 @cache 时,发生了这些事:
1.装饰器在定义时执行
@cache 是一个装饰器工厂,它把 add 函数包装成了一个带缓存的版本。
2.生成一个缓存字典
内部会创建一个 dict(或 LRU 结构),键是参数的元组化表示,值是计算结果。
3.参数如何变成 key?
位置参数 + 关键字参数 → 打包成 (args_tuple, kwargs_frozendict)
所有不可哈希的对象(如 list、dict)会报错(TypeError: unhashable type)
这就是为什么 @cache 要求参数必须可哈希(int、str、tuple、frozenset 等)
4.调用流程
调用 add(3, 4)
先把参数转为 key:((3, 4), {})
查缓存字典,有吗?
有 → 直接返回缓存结果
无 → 真正执行原函数 add(3, 4),得到 7
把 ((3, 4), {}) : 7 存进缓存
返回 7
为什么第二次调用不打印“真正计算”?
因为第二次查缓存命中了,直接跳过了原函数体。
但要注意:
欢迎评论区分享你的使用经验,文章对你有帮助的话,点个赞或收藏,转发给需要提速的朋友,一起写更快更优雅的 Python 代码!
Pandas 3.0 内存优化后,能打得过 Polars 和 DuckDB 吗?