性能优化应优先于代码优雅:高频循环用预分配数组和显式for,数值计算用array/numpy,IO密集用异步或mmap,日志批量刷盘,轻量数据用tuple/NamedTuple,字符串拼接用join,查找用set,计数用Counter,数值计算向量化。
当代码运行明显变慢、内存占用飙升,或在关键路径上成为瓶颈时,就该放下“优雅”去换性能。
像 [x * 2 for x in data if x > 0] 看着干净,但如果 data 是百万级列表且在实时处理中反复执行,生成新列表+遍历两次的开销会很实在。这时候拆成预分配数组 + 显式 for 循环,配合 break 或提前返回,往往快 2–5 倍。
array.array 或 numpy.ndarray 替代普通 list 存数值数据len(seq) 提到外面)itertools.islice 或生成器切片代替 list[start:end] 复制比如用 requests.get(url) 串行抓 100 个网页,代码只有 3 行,但耗时可能是并行请求的 80 倍。此时“优雅”的阻塞调用反而成了性能毒药。
aiohttp + async/await 并发请求
open(..., buffering=8192) 调整缓冲区,或直接 mmap一个只存 3 个字段、生命周期短、不需方法的配置项,用 dataclass 或普通 class 创建几千次,比用 tuple 或 namedtuple 多占 30%–50% 内存,初始化也慢一截。
typing.NamedTuple 或 collections.namedtuple
__slots__ = ('a', 'b', 'c') 节省内存(x*2 for x in data),
比如想“优雅地”用 functools.reduce(operator.add, strings) 拼接长字符串——这其实是 O(n²) 的灾难;而 ''.join(strings) 是 C 层优化过的 O(n)。
join
item in set 而非 item in list
collections.Counter,别手写字典累加numpy 向量化就别写 for,哪怕多引入一行 import性能不是永远压倒可读性,但在明确可观测的瓶颈处,“够快”就是最务实的优雅。