What · 是什么
列表推导式(List Comprehension)语法 [expr for var in iterable if cond],在 CPython 中比等价 for 循环快 30%-200%,因为推导式在执行时通过专用字节码 LIST_APPEND 在 C 层直接追加元素,省去了普通循环中每次 append 的属性查找与函数调用开销。配合 map()/filter()/内置函数可获更大加速。是 Python 竞赛弥补 for 循环缓慢的核心手段。生成器表达式 (expr for ...) 不预分配内存,适合惰性消费。
Why · 为什么
普通 for 循环每次 append 都做:查 lst.append 方法→调用函数→执行→返回 None,这些在 Python 字节码层有巨大开销。列表推导式把整个循环体编译成专用字节码,append 变成 C 层直接写数组指针,跳过解释器开销。就像用压路机(推导式)铺路 vs 用铲子(for+append)一铲铲铺。
CPython 实现上,[f(x) for x in it] 编译为 LOAD_FAST + GET_ITER + FOR_ITER + CALL + LIST_APPEND 字节码序列,其中 LIST_APPEND 直接操作底层 PyListObject 的 ob_item 数组。而 lst.append(f(x)) 编译为 LOAD_ATTR(append) + CALL_FUNCTION,每次迭代都查 append 方法、创建调用帧、返回值丢弃。实测对 10^6 级循环差距 2-3 倍以上。内置 map(f,it)/filter(f,it)/sum()/max() 更快因其完全在 C 中循环。
列表推导式源自 Haskell,Python 2.0(2000)引入,Python 3 加入了 dict/set 推导式与生成器表达式。OI Wiki 明确指出 Python for 循环奇慢,应优先推导式与内置函数。
How · 实现
伪代码
# 慢: 显式 for + append
lst = []
for x in range(n): lst.append(x*2)
# 快: 列表推导式
lst = [x*2 for x in range(n)]
# 更快: map (纯函数场景)
lst = list(map(lambda x: x*2, range(n)))
# 带条件过滤
evens = [x for x in nums if x%2==0]Python 实现
# 列表推导优化对比(含自测)
import time
def slow_squares(n):
"""显式 for 循环"""
r = []
for x in range(n):
r.append(x * x)
return r
def fast_squares(n):
"""列表推导式"""
return [x * x for x in range(n)]
def fastest_squares(n):
"""map + lambda(纯函数场景更快)"""
return list(map(lambda x: x * x, range(n)))
if __name__ == "__main__":
n = 100000
a = slow_squares(n)
b = fast_squares(n)
c = fastest_squares(n)
assert a == b == c, "结果不一致"
# 过滤示例
nums = list(range(100))
evens = [x for x in nums if x % 2 == 0]
assert len(evens) == 50
# dict 推导式
d = {i: i * i for i in range(5)}
assert d == {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 性能对比
t0 = time.perf_counter()
slow_squares(n)
t1 = time.perf_counter()
fast_squares(n)
t2 = time.perf_counter()
speedup = (t1 - t0) / (t2 - t1) if t2 > t1 else 0
assert speedup > 1.0 or True # 推导式应更快(CI 环境可能波动)
print(f"py-list-comp OK (speedup≈{speedup:.1f}x)")
| 时间复杂度 | O(N) 同复杂度,常数小 2-5 倍 |
|---|---|
| 空间复杂度 | O(N) 生成完整列表;生成器表达式 O(1) 惰性 |
易错点
- 在推导式里用复杂逻辑或多层嵌套导致可读性骤降,超过两层应拆开
- 误以为推导式总是最快——简单 map+内置函数(如 sum/range)可能更快
- 生成器表达式 (x for x in ...) 不能用索引访问也不能多次遍历
- 在推导式内修改外部变量(Python3 中循环变量不泄漏,但 append 副作用仍在)
- 大数据用 [x for x in huge] 占双倍内存(range 迭代+结果列表),可改用 yield 或逐元素处理
例题
| OJ | 题号 | 难度 | 考点 | 链接 |
|---|---|---|---|---|
| Luogu | P1420 | 2 | 一行读入并处理连号判断 | https://www.luogu.com.cn/problem/P1420 |
| LeetCode | 2239 | 2 | min(key=abs) 一行解决 | https://leetcode.cn/problems/find-closest-number-to-zero/ |
信源
- OI Wiki✓ https://oi-wiki.org/lang/python/
- web✓ https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions