列表推导式更快,因其绕过Python层append调用开销,直接在CPython内部操作底层C数组,并预分配内存、使用专用LIST_APPEND字节码指令实现双重加速。

列表推导式快,是因为它绕过了 list.append() 调用开销
每次在 for 循环里写 result.append(x),Python 都要执行一次属性查找(result.__getattribute__('append'))、压栈、调用 C 函数、再返回——哪怕 append 是内置方法,这个过程在百万次循环中会累积成可观延迟。
而列表推导式不走这条路。它在 CPython 内部直接操作目标列表的底层 C 数组,元素生成后直接写入内存,完全跳过 Python 层的方法绑定与调用流程。
- 实测:对
range(1_000_000)做平方,for循环平均比推导式慢约 12%–18%,差距随数据量增大而拉宽 - 注意:如果循环体本身很重(比如含 I/O 或复杂函数调用),这部分开销会被掩盖,性能差异就不明显
- 别误以为“只要用推导式就一定快”——若你写的是
[expensive_func(x) for x in huge_list],瓶颈根本不在 append,而在expensive_func
dis 看得见的字节码差异:LIST_APPEND 是关键指令
用 import dis 对比两种写法,能清楚看到底层区别:
dis.dis(lambda: [x*2 for x in range(10)]) # 输出中会出现:LIST_APPEND 2
而等效的 for 循环会反复出现:
LOAD_ATTR 0 (append) CALL_FUNCTION 1 POP_TOP
LIST_APPEND 是 CPython 专为推导式设计的字节码指令,它直接从栈顶取值、写入指定列表对象,无需查属性、不触发 Python 层函数调用协议。
- 这个指令只在推导式(包括列表、集合、字典推导式)中生成,普通代码无法手动触发
- 它依赖于解释器对推导式语法的特殊识别,不是语法糖,是深度集成的优化路径
- 如果你用
exec()动态构造推导式字符串,依然能获得该优化——只要最终被解析为推导式语法节点
预分配内存 + C 层循环,双重加速
CPython 在执行列表推导式前,会尝试估算输出长度(例如 range(n) 已知长度,map() 则不行),从而预先分配足够内存块,避免多次 realloc 和数据拷贝。
同时,整个迭代逻辑(for x in iterable 部分)是在 C 层实现的,不像 Python 层 for 循环那样每轮都要做变量绑定、异常检查、迭代器 __next__ 调用等解释器开销。
- 对已知长度的可迭代对象(如
range、tuple、list),预分配效果最明显 - 对生成器或自定义迭代器,无法预估长度,仍会动态扩容,但至少省掉了
append调用成本 - 不要指望推导式能优化算法复杂度——它只减少“构建容器”这个动作的额外开销,不改变你写的表达式本身的计算量
什么时候不该用列表推导式?
快 ≠ 万能。以下情况强行套用反而坏事:
- 需要提前中断(
break)或条件跳过(continue多层逻辑)——推导式不支持语句,只能靠if过滤,无法处理副作用 - 结果集极大,又不需要一次性全加载到内存 —— 此时应换
(x**2 for x in range(n))生成器表达式 - 循环体含调试
print、日志记录、数据库写入等副作用操作 —— 推导式语义上不鼓励副作用,且难以断点调试 - 嵌套三层以上或混合多个
if/else分支 —— 可读性暴跌,维护成本远超那几十毫秒性能收益
真正容易被忽略的点是:性能优势只在“纯计算+构建容器”这一狭窄路径下稳定成立;一旦掺入 I/O、锁、异常处理或复杂控制流,推导式的底层优势就迅速被掩盖,甚至因强制一次性求值带来内存压力。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











