性能优化不是从改算法开始的,往往先从写好每一行代码入手。Python 里,一些小小的调整就能让程序运行快上几倍。下面这些技巧都是生产环境验证过的,实用且易于落地。
优先使用内置函数和标准库
Python 的内置函数和标准库大多是用 C 实现的,执行速度远快于你自己用纯 Python 写的循环。
- 用
sum()别自己写循环
想对一个列表求和,写 for 循环不仅代码长,也更慢:
# 自己写循环,慢
total = 0
for x in data:
total += x
# 内置 sum,快几倍
total = sum(data)
- 用
map()、filter()和列表推导式
它们底层走 C 循环,比显式的 for 循环快。
# 慢
result = []
for item in items:
result.append(func(item))
# 快:列表推导
result = [func(item) for item in items]
# 也快:map
result = list(map(func, items))
- 善用
collections、itertools和functools
比如 Counter 统计词频、deque 实现高效队列、lru_cache 缓存重复计算,都是标准库里的性能利器。
减少循环层级与循环内操作
循环是 Python 里最耗时的结构之一,循环层数一多,耗时就以乘积级增长。
- 将不变的运算提到循环外
不要在循环里重复计算或访问不变量。
# 不好:每次循环都算 len(data)
for i in range(len(data)):
process(data[i])
# 好
n = len(data)
for i in range(n):
process(data[i])
- 用
break或continue提前退出
一旦找到所需结果就立刻跳出循环,避免无谓遍历。
- 考虑用集合或字典的 O(1) 查找替代列表的 O(n) 遍历
频繁的成员检查(if item in collection)应该使用 set 或 dict,而不是 list。
用生成器替代列表
生成器是“惰性求值”的,只在需要时才产生数据,能显著节省内存,同时减少一次性构建列表的时间开销。
- 生成器表达式
# 立刻构建完整列表,占用大内存
squares = [x**2 for x in range(10**7)]
# 生成器,几乎不占内存
squares_gen = (x**2 for x in range(10**7))
- 生成器函数
yield 一个个返回值,而不是一次性 return 一个巨大列表。
- 用
map、filter获得迭代器
Python 3 中它们返回迭代器而非列表,本身就是惰性操作。
合理选用数据结构
数据结构的操作复杂度直接决定性能天花板。
- 查找频繁用
dict或set,别用list。字典的键查找为 O(1),列表为 O(n)。 - 需要两头快速插入/删除用
collections.deque,而不是列表。列表头部插入 O(n),deque 两端操作 O(1)。 - 需要有序的键值对用
OrderedDict或 Python 3.7+ 的普通dict(已保持插入顺序),但不要为了排序而频繁插入删除。 - 绑定少量固定名字的元组用
namedtuple,避免创建完整类,内存更省、访问更快。
局部变量与属性访问优化
- 将全局变量、模块属性赋值给局部变量
局部变量查找速度比全局变量快,因为局部变量在一个数组里用索引访问,而全局变量或对象属性需要字典查找。
import math
def slow():
# 每次循环都在全局空间中找 math.sin
for x in range(1000):
y = math.sin(x)
def fast():
sin = math.sin # 变成局部变量
for x in range(1000):
y = sin(x)
- 减少对象属性链式访问
多次使用同一个对象的深层属性,比如 obj.a.b.c,应该先赋给一个局部变量,循环里直接使用该变量。
其他高频实用技巧
- 字符串拼接:当拼接多个字符串时,用
''.join(list_of_strs)比+或+=快得多,因为避免了大量临时字符串对象的创建。 - 使用
slots节省内存:类的属性固定时,定义slots可以阻止实例生成dict,大幅减少内存占用并加快属性访问。 - 避免在热点代码中使用装饰器包装:装饰器可能有函数调用开销,对性能极度敏感的代码可以暂时去掉它们。
- 善用
functools.lru_cache:如果一些纯函数会被反复用相同参数调用,加上缓存装饰器能瞬间提速。
这些技巧不需要改变程序整体架构,也不要求你啃算法书,只要在写代码时多留一点心,就能把 Python 程序的“潜藏性能”挤出来。当然,优化前记得先用 timeit 或 cProfile 找到真正的瓶颈——优化那 20% 的热点,往往能解决 80% 的性能问题。