+= 循环拼接字符串很慢,因字符串不可变,每次操作都需分配新内存并复制全部内容,时间复杂度近 o(n²);推荐用 list.append() + ''.join() 或 stringio。

为什么 += 在循环里拼接字符串很慢?
Python 中字符串是不可变对象,每次用 += 拼接都会创建新字符串,并把旧内容完整复制过去。循环 10000 次?就可能触发上万次内存分配 + 复制,时间复杂度接近 O(n²)。这不是“有点慢”,是真实卡顿,尤其处理日志、HTML 片段或 CSV 行时。
- 实际现象:
for i in range(10000): s += str(i)可能比预期慢 10–100 倍 - 根本原因:底层 CPython 每次都调用
PyUnicode_Append,且无法预知最终长度,无法预留空间 - 注意:这个开销在小数据(
用 list.append() + ''.join() 是最稳方案
这是 CPython 官方文档明确推荐的方式,核心是把“动态增长”交给可变的 list,最后一次性合成字符串。
list的append()平摊时间复杂度是 O(1),内部有扩容策略(通常是 1.125 倍增长)''.join()接收序列,会先遍历一次算总长度,再分配一块刚好够用的内存,然后逐个拷贝 —— 零冗余复制-
示例:
parts = [] for item in data: parts.append(str(item)) parts.append(',') result = ''.join(parts) # 仅一次分配 + 一次拷贝 别写
''.join([str(x) for x in data]):列表推导式会先建完整列表,再传给join,没问题;但若中间有逻辑分支,还是显式append更可控
什么情况下可以放心用 f-string 或 %?
当拼接操作固定、少量、无循环时,f-string 不仅可读性高,性能也极好 —— CPython 对常量 f-string 做了编译期优化。
- 适用场景:日志模板
f"[INFO] {name} processed {count} items" - 不适用场景:在
for循环内写s = f"{s}{x}"—— 这等价于s += str(x),退化回不可变拼接 -
%和.format()在单次拼接中性能与 f-string 接近,但可读性和安全性(如自动转义)不如 f-string,无特殊理由不必用
io.StringIO 适合带格式写入的长文本构建
如果你不只是拼字符串,还要做类似“写文件”的操作(换行、缩进、条件插入块),StringIO 提供了类文件接口,底层缓冲区可动态扩容,且避免了反复构造中间字符串。
-
示例:
from io import StringIO buf = StringIO() for line in lines: buf.write(line) buf.write('\n') result = buf.getvalue() # 最后一次性取值 优势:支持
write()、writelines()、seek()等,适合生成配置、SQL、Markdown 等结构化文本注意:
StringIO的getvalue()仍会复制全部内容到新字符串,所以它不省内存,只省中间字符串对象的创建开销
真正容易被忽略的是:不要因为听说“join 快”就到处套用。如果只有两次拼接,比如 a + b + c,CPython 会直接优化成单次分配,此时写 ''.join([a,b,c]) 反而多建列表、多一次函数调用。优化的前提是确认瓶颈真在拼接上,而不是别的地方。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











