恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
几行代码让 Python 函数执行快 30 倍
首页
资讯中心
/
几行代码让 Python 函数执行快 30 倍
几行代码让 Python 函数执行快 30 倍
发布时间:2026/10/17 9:39:52
前言先把话说在前面不存在几行代码让所有 Python 函数都稳定快 30 倍这回事。30 倍是一个具体数字而实际提速取决于函数在做什么、瓶颈在哪、跑在哪个 解释器上绝不可能有一个放之四海而皆准的倍数。本文不会给出任何提速 N 倍的 承诺而是讲清楚哪些做法通常更快、为什么更快并给出一段你可以自己运行、 亲眼测出结果的基准代码。另一个要破除的误解是优化就是到处加小技巧。真正的顺序应该是先测量、找到 瓶颈、再优化。如果函数的时间花在网络请求或磁盘 I/O 上把循环里的属性访问 抠得再细也几乎没用反过来一个纯计算的递归函数如果重复算同一批子问题一个 缓存装饰器就能改变数量级。最后提醒本文提到的很多更快都属于CPython 的实现细节比如局部变量比 全局变量查找快换解释器、换版本甚至换成 3.13 起实验性的自由线程构建结论 都可能不同。任何优化都要在你自己的环境里量一遍。一、为什么不能承诺30 倍优化的收益来自消除了多少无效工作而不是来自加了几行魔法。可以分成几类减少重复计算把已经算过的结果缓存下来重复部分越多收益越大。降低查找开销变量、属性、方法的查找有成本把高频查找提到循环外。选对算法 / 数据结构这一步往往才是数量级差别但和几行代码无关。减少内存拷贝避免不必要地物化大列表。其中几行代码能碰到的主要是前两类。后面两类更多是设计问题。二、缓存重复计算functools.lru_cache 与 cache对纯函数相同输入必得相同输出、无副作用来说把结果缓存起来是最直接的 加速手段。标准库提供了两个装饰器functools.lru_cache(maxsize128, typedFalse)带容量上限的最近最少使用缓存超出上限时淘汰最久未用的条目。functools.cachePython 3.9 起等价于lru_cache(maxsizeNone)即不设上限。# 适用于 Python 3.9from functools import cachecachedef fib(n):if n 2:return nreturn fib(n - 1) fib(n - 2)print(fib(30)) # 832040且大大减少了重复递归lru_cache在 Python 3.8 及更早版本也能用只是没有functools.cache这个 简写写成lru_cache(maxsizeNone)即可。三、把全局查找变成局部查找在 CPython 里函数内访问局部变量用的是快速路径字节码LOAD_FAST 访问全局变量要走字典查找LOAD_GLOBAL。所以把高频使用的全局名字在 函数开头绑定成局部名字通常能省下一点查找开销。# 适用于 Python 3.8from math import sqrtdef hypot_many(pairs):local_sqrt sqrt # 绑定成局部名字return [local_sqrt(x * x y * y) for x, y in pairs]同理把每次都算一遍的表达式提到循环外# 适用于 Python 3.8def scale(values, factor, offset):# 把不变的表达式放到循环外shift factor * offsetreturn [v * factor shift for v in values]再次强调这类改动是通常略快不是一定快 30 倍实际差别取决于循环 体有多重——循环体越轻查找开销占比越大。四、避免在热循环里重复做属性查找每次obj.attr都要查一次属性。在很热的循环里把方法或属性提到循环外能少做 很多次查找# 适用于 Python 3.8def double_all(items):result []append result.append # 绑定一次循环里反复用for x in items:append(x * 2)return result但要清醒如果循环体里有真正的重活比如字符串处理、网络 I/O这点查找 开销可以忽略。先测量再决定值不值得牺牲可读性。五、选对写法能少算就少算有些提速其实是沟通哪些工作可以不做用生成器表达式代替大列表避免一次性物化降低内存峰值但注意生成器只能遍历一次。用str.join(...)拼接大量字符串而不是在循环里反复s piece。用字典 / 集合做成员判断而不是在列表里线性扫描。用内置函数sum、min、max、any、all让它走 C 实现而不是手写Python 循环。# 适用于 Python 3.8words [alpha, beta, gamma]# 推荐join 一次拼好line ,.join(words)# 推荐用集合做成员判断allowed {yes, no, maybe}print(yes in allowed) # True通常比在列表里扫更快六、自己动手量timeittimeit.timeit(stmtpass, setuppass, timer..., number1000000, globalsNone)和timeit.repeat(stmtpass, setuppass, timer..., repeat5, number1000000, globalsNone)是做微基准的标准工具timer参数不传就用平台默认的最佳计时器。下面这段 可以直接复制运行# 适用于 Python 3.8import timeitsetup_plain def fib(n):if n 2:return nreturn fib(n - 1) fib(n - 2)setup_cached from functools import lru_cachelru_cache(maxsizeNone)def fib(n):if n 2:return nreturn fib(n - 1) fib(n - 2)plain min(timeit.repeat(fib(25), setupsetup_plain, repeat5, number1))cached min(timeit.repeat(fib(25), setupsetup_cached, repeat5, number1))print(f普通递归: {plain:.6f} 秒)print(f加缓存 : {cached:.6f} 秒)说明repeat返回每轮的耗时列表取min是为了避开偶尔被系统调度干扰的 那几次。注意缓存版本在第一次跑完之后缓存就热了所以后续几轮量的是命中 缓存的速度这正是我们想展示的差异。命令行里也能量python -m timeit -s from math import sqrt sqrt(2)七、常见优化手段对照手段收益来源适用前提functools.cache/lru_cache去掉重复计算函数是纯函数、参数可哈希局部绑定全局名少走一次字典查找名字在热循环里高频使用循环外提表达式不重复计算不变量表达式确实与循环变量无关局部绑定方法少做属性查找循环很热、循环体很轻用内置函数走 C 实现有对应的内置函数选对数据结构改变复杂度常出现在成员判断、查找环节常见坑点给不可哈希的参数加缓存python from functools import lru_cache # ❌ 列表不可哈希调用时抛 TypeError lru_cache(maxsizeNone) def f(xs): return sum(xs) # ✅ 传不可变类型 lru_cache(maxsizeNone) def g(xs): return sum(xs) g((1, 2, 3))给有副作用的函数加缓存python from functools import lru_cache # ❌ 第二次调用不会真的写文件副作用被跳过 lru_cache(maxsizeNone) def save(path, data): ... # ✅ 缓存只用在纯函数上指望微优化解决 I/O 瓶颈python # ❌ 时间花在网络等待上改循环写法没用 for url in urls: data fetch(url) # 真正的瓶颈在这里 # ✅ 先定位瓶颈再决定优化手段过早优化python # ❌ 还没测就先把代码改得又长又难读 # ✅ 先测量只优化热点timeit 用法错误导致结果不可信python # ❌ 把 setup 里的准备工作算进了计时 # ✅ 准备工作放 setup被计时的只放 stmt另外单次运行波动大要用repeat多跑几轮再取min。误用 lru_cache 的默认容量python from functools import lru_cache # ❌ 参数取值很多时默认 maxsize128 会频繁淘汰 lru_cache def f(n): ... # ✅ 要么显式设容量要么明确要无上限 lru_cache(maxsizeNone) def g(n): ...把通常更快当成必然更快python # ❌ 不测就断言某写法一定快 # ✅ 在自己的数据、自己的版本上量一遍再下结论总结思路代表做法少算functools.cache/lru_cache少查局部绑定、循环外提表达式少拷生成器、join、内置函数选对字典 / 集合代替线性扫描先量timeit测再决定改不改几行代码快 30 倍更像是一句吸引眼球的宣传语而不是可以复现的承诺。真正 可靠的做法是用timeit找到瓶颈针对瓶颈选对手段然后重新测一遍确认 有效。缓存和局部绑定能帮上忙但它们降的是常数与重复开销改变不了算法本身 的复杂度——那才是数量级差别真正来源的地方。