算法知识图谱总览 / Python竞赛特性族 / 列表推导优化

列表推导优化

P0basicdeepG-E-3

What · 是什么

Python for循环极慢,用列表推导式/filter/map/内置函数替代显式循环获10x+加速

列表推导式(List Comprehension)语法 [expr for var in iterable if cond],在 CPython 中比等价 for 循环快 30%-200%,因为推导式在执行时通过专用字节码 LIST_APPEND 在 C 层直接追加元素,省去了普通循环中每次 append 的属性查找与函数调用开销。配合 map()/filter()/内置函数可获更大加速。是 Python 竞赛弥补 for 循环缓慢的核心手段。生成器表达式 (expr for ...) 不预分配内存,适合惰性消费。

Why · 为什么

直觉 · Intuition

普通 for 循环每次 append 都做:查 lst.append 方法→调用函数→执行→返回 None,这些在 Python 字节码层有巨大开销。列表推导式把整个循环体编译成专用字节码,append 变成 C 层直接写数组指针,跳过解释器开销。就像用压路机(推导式)铺路 vs 用铲子(for+append)一铲铲铺。

原理 · Principle

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 中循环。

历史 · History

列表推导式源自 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)")
PyPy 提示PyPy JIT 会把简单 for+append 循环编译为接近推导式的速度,差距缩小甚至消失。但推导式仍更简洁可读。PyPy 下 map/filter 性能与推导式持平。建议无论运行时为何都优先推导式——代码在 CPython 也有保险。
时间复杂度O(N) 同复杂度,常数小 2-5 倍
空间复杂度O(N) 生成完整列表;生成器表达式 O(1) 惰性
注意 · 局限1) 嵌套过深的推导式可读性差,超过两层应改用普通循环;2) 推导式内执行有副作用的函数(如 print)调试困难;3) 生成器表达式多次遍历需重新创建。

易错点

例题

OJ题号难度考点链接
LuoguP14202一行读入并处理连号判断https://www.luogu.com.cn/problem/P1420
LeetCode22392min(key=abs) 一行解决https://leetcode.cn/problems/find-closest-number-to-zero/
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
下游 · 依赖本知识点

信源

✓ = 已核验 · △ = 未核验