列表推导式比等效for循环快,因其绕过Python层append方法调用,直接使用CPython内部的LIST_APPEND字节码,避免LOAD_ATTR和CALL_FUNCTION等开销。

列表推导式比等效的 for 循环快,核心不是“语法糖”,而是它绕过了 Python 层的 append 方法调用开销,在 CPython 解释器内部直接使用了更底层的 LIST_APPEND 字节码。
为什么 LIST_APPEND 比 result.append() 快?
每次在 for 循环里写 result.append(x),Python 都得做三件事:查找 result 对象上的 append 方法(LOAD_ATTR)、构造参数、再执行函数调用(CALL_FUNCTION)。这些全是 Python 层开销。
而列表推导式对应的字节码里,直接是 LIST_APPEND —— 这是一个 C 实现的、专用于向正在构建的列表追加元素的指令,不经过属性查找和函数调用栈。
- 你可以用
dis.dis("[x*2 for x in range(10)]")和dis.dis("for x in range(10): result.append(x*2)")对比看字节码差异 - 把
append提前绑定到局部变量(如_append = result.append)能缩小差距,但依然慢于列表推导式,因为仍需CALL_FUNCTION - 这个优势只在「构建新列表」时成立;如果只是遍历或有副作用(比如发 HTTP 请求),列表推导式不仅不快,还可能误导语义
什么时候列表推导式反而更慢?
速度优势依赖于解释器对「纯表达式」的优化。一旦推导式里混入函数调用、复杂条件或嵌套逻辑,性能提升会迅速衰减,甚至因可读性下降导致维护成本激增。
- 含多次函数调用:如
[process(item) for item in data if validate(item) and check_perm(item)],每次都要查函数名、进栈、出栈 - 隐式创建中间对象:如
[x.strip().upper() for x in strings],每轮都新建字符串对象,GC 压力可能反超显式循环中的复用逻辑 - 输入可迭代对象不支持长度预测(比如生成器),CPython 无法预分配内存,导致多次扩容,抵消部分 C 层优势
range 和 list 作为输入时表现不同
列表推导式的加速效果在不同输入类型下并不稳定。用 range(不可变、长度已知)时,CPython 可提前分配目标列表内存;但用自定义生成器或文件迭代器时,就只能边迭代边扩容。
- 测试时别只用
range(1000000)得出结论,换成(x for x in range(1000000))再测一次 -
list输入最快,因为长度确定 + 连续内存访问局部性好;tuple次之;生成器最不可控 - 真正影响端到端耗时的,往往不是推导式本身,而是前后数据加载或后续处理——别过早优化推导式,先用
cProfile确认它真是瓶颈
真正容易被忽略的是:列表推导式快的前提是「你只需要一个新列表」。如果中途要 break、要记录索引、要捕获异常、或者想复用已有列表对象,硬套推导式只会让代码更难 debug,也未必更快。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











