推导表达式(Comprehensions)是 Python 最优雅的特性之一:用单行代码完成循环、过滤、转换三件事。它不是语法糖,而是一种声明式的思维方式——你告诉 Python 你要什么,而不是怎么做。

列表推导

语法[expression for item in iterable if condition]

# 传统方式:命令式,告诉 Python "怎么做"
squares = []
for i in range(10):
    if i % 2 == 0:
        squares.append(i ** 2)

# 推导式:声明式,告诉 Python "要什么"
squares = [i ** 2 for i in range(10) if i % 2 == 0]
# [0, 4, 16, 36, 64]

嵌套循环:后面的 for 相当于内层循环

# 笛卡尔积
pairs = [(x, y) for x in range(3) for y in range(2)]
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

# 矩阵展平
matrix = [[1, 2, 3], [4, 5, 6]]
flat = [elem for row in matrix for elem in row]
# [1, 2, 3, 4, 5, 6]

条件表达式:用三元运算符处理 if-else

# 偶数平方,奇数取负
result = [x ** 2 if x % 2 == 0 else -x for x in range(5)]
# [0, -1, 4, -3, 16]

字典推导

语法{key_expr: value_expr for item in iterable if condition}

# 键值互换
original = {'a': 1, 'b': 2}
swapped = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b'}

# 带过滤
scores = {'alice': 85, 'bob': 62, 'charlie': 90}
passed = {name: score for name, score in scores.items() if score >= 70}
# {'alice': 85, 'charlie': 90}

集合推导

语法{expression for item in iterable if condition}

# 自动去重
duplicates = [1, 1, 2, 2, 3, 3]
unique = {x for x in duplicates}
# {1, 2, 3}

# 字符集
chars = {c for c in 'hello world' if c.isalpha()}
# {'h', 'e', 'l', 'o', 'w', 'r', 'd'}

生成器推导

语法(expression for item in iterable if condition)

关键区别:惰性求值。列表推导一次性生成所有元素,生成器推导按需产出,内存占用恒定。

# 列表推导:立即计算,占用 O(n) 内存
squares_list = [x ** 2 for x in range(10 ** 6)]  # ~40MB

# 生成器推导:惰性计算,占用 O(1) 内存
squares_gen = (x ** 2 for x in range(10 ** 6))  # 几乎不占内存

# 逐个获取
next(squares_gen)  # 0
next(squares_gen)  # 1

实际场景:处理大文件

# 统计日志中 ERROR 行数,不一次性加载整个文件
error_count = sum(1 for line in open('app.log') if 'ERROR' in line)

海象运算符(Python 3.8+)

推导表达式中可以赋值并复用,避免重复计算:

# 不好的写法:计算两次 len(word)
long_words = [word.upper() for word in words if len(word) > 5 if len(word) < 10]

# 海象运算符:计算一次,复用结果
long_words = [upper for word in words if 5 < (length := len(word)) < 10 for upper in [word.upper()]]

# 更实际的例子:过滤并保留计算结果
results = [score for x in data if (score := expensive_calculation(x)) > threshold]

性能对比

推导表达式比等效的 for 循环快 10-20%,因为迭代在 C 层完成,避免了字节码层面的循环开销。

import timeit

# 传统循环
def traditional():
    result = []
    for i in range(10000):
        if i % 2 == 0:
            result.append(i ** 2)
    return result

# 列表推导
def comprehension():
    return [i ** 2 for i in range(10000) if i % 2 == 0]

# 测试
print(timeit.timeit(traditional, number=1000))
print(timeit.timeit(comprehension, number=1000))

可读性边界

推导表达式的核心价值是简洁,但简洁不能牺牲可读性。

好的推导:逻辑一目了然

# 清晰:一眼看懂意图
evens = [x for x in range(100) if x % 2 == 0]

坏的推导:嵌套太深,条件太多

# 糟糕:需要停下来分析
result = [x*y for x in range(10) for y in range(10) if x % 2 == 0 if y > 5]

# 改写为循环
result = []
for x in range(10):
    if x % 2 == 0:
        for y in range(10):
            if y > 5:
                result.append(x * y)

经验法则

  • 超过两层嵌套 → 用循环
  • 超过两个条件 → 用循环
  • 需要调试 → 用循环

常见陷阱

1. 修改迭代变量不影响原序列

nums = [1, 2, 3]
[x + 1 for x in nums]  # 返回 [2, 3, 4]
print(nums)            # 原序列不变:[1, 2, 3]

2. 变量泄漏(Python 3 已修复)

# Python 2:循环变量会泄漏到外部作用域
# Python 3:推导表达式有自己的作用域
[x for x in range(5)]
print(x)  # NameError: name 'x' is not defined

3. 元组推导不存在

# 这不是元组推导,是生成器推导!
gen = (x for x in range(5))
type(gen)  # <class 'generator'>

# 要生成元组,用 tuple()
tup = tuple(x for x in range(5))

总结

类型语法返回内存
列表推导[...]listO(n)
字典推导{k: v ...}dictO(n)
集合推导{...}setO(n)
生成器推导(...)generatorO(1)

推导表达式的本质是用声明式思维替代命令式循环。当你发现自己写 result = []; for x in ...: result.append(...) 时,停下来,用推导表达式重写它。但记住:可读性永远优先于简洁性