当前位置:首页>python>Python @cache 一行代码提速百倍?运行原理彻底拆解,别再手动写缓存了!

Python @cache 一行代码提速百倍?运行原理彻底拆解,别再手动写缓存了!

  • 2026-10-07 15:55:41
Python @cache 一行代码提速百倍?运行原理彻底拆解,别再手动写缓存了!

<摘要>

@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 resultprint(fib(35))  # 瞬间出结果

这样确实快了很多,但有几个明显的问题:

1.全局变量污染:cache_dict 是全局的,如果多个函数都想缓存,就得写多个字典,或者搞得很乱。

2.代码侵入性强:每次写新函数都要手动加这一套“查缓存 → 计算 → 存缓存”的逻辑,重复代码很多。

3.不优雅:如果函数参数是列表、字典等可变对象,或者有关键字参数,手动处理会更麻烦。

4.维护麻烦:想加缓存上限、想清空缓存、想统计命中率,都得自己再写一堆代码。

有没有一种方法,能一行代码就让函数自动记住所有调用结果,

而且不污染全局、不用改函数内部逻辑、还能处理各种参数类型?

答案就是 @cache(或它的升级版 @lru_cache)。

使用 @cache

我们直接给上面的函数加一行装饰器:

from functools import cache@cachedef 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 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

为什么第二次调用不打印“真正计算”?

因为第二次查缓存命中了,直接跳过了原函数体。

但要注意:

  • 参数必须可哈希
  • 缓存会一直占用内存(除非用 lru_cache + maxsize)
  • 不适合参数变化很大、调用次数极多的场景(缓存会爆炸)

欢迎评论区分享你的使用经验,文章对你有帮助的话,点个赞或收藏,转发给需要提速的朋友,一起写更快更优雅的 Python 代码!

往期回顾BREAK AWAY

Pandas 3.0 正式版来了!比你想象中更狠的两个改变

Pandas 3.0 内存优化后,能打得过 Polars 和 DuckDB 吗?

Pandas3.0用Rust重写后性能暴增5倍?
2025年终复盘:停更又复活,关于流量、AI与技术号的几句真心话
2025年最受欢迎的编程语言:热度背后的逻辑与趋势

最新文章

随机文章