提升Python代码性能的五个小技巧 Python 慢但慢的根源往往不是语言本身而是你写代码的方式。很多开发者一边抱怨 Python 是“胶水语言”一边把手写的三层 for 循环当宝贝供着。性能提升这件事从来不需要你重写整个项目——几个小技巧就能让代码快一个数量级。下面这五个技巧每一个都能在真实工程中显著缩短运行时间看完你可以直接拿去改造现有代码。用内置函数和列表推导式代替手写循环新手最容易犯的错误就是拿 Python 当 C 写遍历列表用for i in range(len(lst))然后通过索引取值计算平方还要自己写result []再加循环。这些手写循环每多一层Python 解释器就要多执行一组字节码而内置函数和列表推导式往往直接调用 C 语言实现的底层函数速度差距可达 10 倍以上。看一个常见场景将字符串列表中的每个元素转换成大写并拼接。手写循环版本需要先创建空列表逐元素调用.upper()再用str.join合并。而只用一行.join([s.upper() for s in words])列表推导式内部以 C 速度迭代join也直接操作 C 数组。实测 10 万长度的列表手写循环耗时约 0.15 秒列表推导式仅需 0.02 秒。记住凡是能用map、filter、sum、any、all解决的问题别写循环。例如计算列表所有元素的平方和sum(x2 for x in lst)比手写循环快近一倍因为sum在 C 层累加且生成器表达式避免了中间列表。但这些内置函数也不是万能灵药。当逻辑过于复杂比如需要多重条件判断并修改外部变量强行套用map或reduce反而会让代码难以理解。取舍原则是用一行代码解决 80% 的简单迭代剩下的 20% 复杂逻辑才考虑手写循环或改用 NumPy。还要注意列表推导式如果用来创建过大的中间列表会消耗内存此时应改用生成器表达式圆括号而非方括号。例如sum(x2 for x in huge_range)不会一次性生成所有平方值内存效率极高。缓存与记忆化让重复计算永不发生很多性能瓶颈源于同一个计算被反复执行。例如斐波那契数列的递归版本时间复杂度是 O(2^n)因为每一层都在重复计算。用 Python 的functools.lru_cache装饰器或functools.cachePython 3.9一行代码就能将指数级复杂度降为线性。原理很简单函数每次调用时装饰器会检查参数是否已经计算过若已计算则直接返回缓存结果跳过整个函数体。看一个更真实的场景Web 后端经常需要根据用户 ID 从数据库查询其权限列表如果同一个请求内多次查询同一个用户就会浪费数据库连接。用一个装饰器lru_cache(maxsize128)包住查询函数短时间内相同 ID 的请求直接命中缓存。注意 lru_cache 默认按 LUR最近最少使用策略淘汰maxsize 可根据业务 QPS 设定设为 None 可无限缓存但小心内存泄漏。还有更轻量的cache本质上也是 lru_cache(maxsizeNone)。对于字典或列表这类可变对象lru_cache默认不能哈希需要转换成可哈希形式如元组。另外记忆化不仅适用于函数内递归也适用于任何“计算成本高、输入有限”的函数。比如图片处理中的颜色映射表、正则表达式的编译结果使用re.compile并缓存编译对象。一句话如果函数多次用相同参数调用并且返回结果不随外部状态改变就应该缓存。但也要小心副作用——缓存函数应该是纯函数否则缓存的结果可能过时。选择正确的数据结构字典 vs 列表的差距是数量级的Python 内置的数据结构各有所长但很多开发者用错了场景。一个经典例子需要检查某个元素是否在集合中有人用列表if item in my_list复杂度 O(n)改用集合if item in my_set复杂度 O(1)。当列表长度达 10 万时集合查找快 5000 倍以上。根本原因是列表底层是数组查找需要线性扫描而集合和字典底层是哈希表平均常数时间。另一个常见问题频繁在列表头部插入或删除元素。list.insert(0, item)和list.pop(0)需要移动后面所有元素时间复杂度 O(n)。如果需要频繁操作两端应该用collections.deque其两端插入删除都是 O(1)。例如用双端队列实现滑动窗口或广度优先搜索性能提升非常明显。选择数据结构的黄金法则是先明确你最频繁的操作是什么再查表选择最合适的数据结构。比如频繁按键值查找用 dict需要保持插入顺序且查找快用 dictPython 3.7 已保证顺序需要去重并保持顺序用dict.fromkeys的键视图其实是 dict需要频繁交集并集用 set。还有一种被忽视的数据结构collections.defaultdict和collections.Counter。前者避免了每次检查键是否存在的if key in dict分支后者直接实现计数逻辑二者都用 C 层代码优化。手动编写if key not in dict: dict[key] 0这种代码不仅慢还增加分支预测失败的风险。使用defaultdict(int)后dict[key] 1直接工作代码更简洁且执行更快。类似地Counter.most_common()内部使用堆排序比手动排序快得多。生成器与惰性求值别让内存成为性能杀手当处理大数据集时很多人习惯先用列表推导式生成完整结果再遍历——如果数据量超过内存程序直接崩溃。生成器generator和迭代器iterator的核心优势是“按需生产”不一次性占用全部内存。例如从一个 10 GB 的日志文件中逐行读取并统计错误数用生成器表达式(line for line in file if ERROR in line)不会把整个文件读入内存每次只读一行总内存消耗恒定。但生成器还有一个常被忽略的好处避免了中间列表的分配和回收开销从而提升整体执行速度。比如sum(x2 for x in range(107))比sum([x2 for x in range(107)])不仅节省内存速度也更快因为后者需要分配一个 1000 万个元素的列表再交给 sum而生成器直接迭代。在 PyPy 或 CPython 3.10 中生成器甚至可以利用更高效的字节码。此外itertools模块提供了大量基于生成器的高效工具比如itertools.chain合并多个迭代器而不复制元素itertools.groupby按 key 分组但返回的是迭代器itertools.product笛卡尔积也是惰性生成。很多嵌套循环可以用itertools.product改写不仅代码更清晰而且底层用 C 实现迭代逻辑比 Python 层手动嵌套快 2-3 倍。例如生成所有可能的密码组合for a,b,c in itertools.product(‘abc’, repeat3):。但生成器并不是万能的。如果需要多次访问相同数据或者必须索引访问生成器就不合适了——此时应该用列表或 tuple 缓存结果。还要注意生成器只能遍历一次重复遍历需要新建。一个简单的判断准则如果数据被消费一次后不再需要就用生成器如果需要随机访问或多次遍历就转为列表。向量化与并行超越 Python 解释器的桎梏上面四个技巧都是在 Python 解释器内优化但 CPython 的全局解释器锁GIL和字节码执行效率始终是天花板。要真正突破性能瓶颈必须把计算“降级”到 C 扩展层或用多进程绕开 GIL。最常用的向量化工具是 NumPy它将循环操作搬到 C 层以数组为单位执行速度比 Python 列表快几十倍。例如计算两个百万元素数组的点积np.dot(a, b)大约 1 毫秒而纯 Python 手写循环需要 2 秒以上。NumPy 的威力不仅在于速度还在于表达力。用np.where、np.select可以避免显式 if-else 循环用np.vectorize可以将任意函数向量化虽然也是循环但由 C 执行。作为经验法则如果代码中出现多层 for 循环并且内部做的是数值运算、统计、线性代数就应该立刻考虑用 NumPy 替代。比如图像处理中 RGB 转灰度gray 0.299R 0.587G 0.114B如果 R、G、B 是 NumPy 数组一行搞定如果是嵌套循环代码又长又慢。当计算无法用 NumPy 一口气向量化时比如有复杂的条件分支或依赖前一步结果考虑用多进程或并发。Python 的多线程因为 GIL 不适合 CPU 密集型任务但多进程multiprocessing.Pool可以充分利用多核 CPU。对于每个任务耗时超过 0.1 秒的 CPU 计算多进程通常能获得近乎线性的加速。需要注意的是进程间通信和序列化开销——如果每次传输的数据很大反而不如单线程。更好的选择是结合 NumPy 的共享内存 multiprocessing.shared_memoryPython 3.8避免拷贝。最后如果以上所有技巧都用上了还是不够快那就考虑用 Cython 编译关键函数或者用 Numba 的 JIT 编译在循环前加njit。Numba 能将数值计算循环直接编译成机器码性能媲美 C。一句话总结先分析瓶颈再用合适的技术对瓶颈降维打击——从内置函数到缓存到数据结构到惰性求值再到向量化/并行成本逐步提高收益也越来越大。