Python 推导表达式
推导表达式(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 defined3. 元组推导不存在
# 这不是元组推导,是生成器推导!
gen = (x for x in range(5))
type(gen) # <class 'generator'>
# 要生成元组,用 tuple()
tup = tuple(x for x in range(5))总结
| 类型 | 语法 | 返回 | 内存 |
|---|---|---|---|
| 列表推导 | [...] | list | O(n) |
| 字典推导 | {k: v ...} | dict | O(n) |
| 集合推导 | {...} | set | O(n) |
| 生成器推导 | (...) | generator | O(1) |
推导表达式的本质是用声明式思维替代命令式循环。当你发现自己写 result = []; for x in ...: result.append(...) 时,停下来,用推导表达式重写它。但记住:可读性永远优先于简洁性。
