列表推导式
列表推导式(list comprehension)提供了一种简洁、可读的方式来创建列表,将循环和条件判断压缩到一行表达式中。相比等价的 for 循环,推导式通常执行更快,因为它在 C 层面完成迭代。
基本语法
最简形式为 [expression for item in iterable],对可迭代对象中的每个元素求值表达式,收集结果组成新列表:
# 传统写法
squares = []
for x in range(10):
squares.append(x ** 2)
# 推导式写法
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
表达式可以是任意合法 Python 表达式,包括函数调用、方法调用和复杂运算:
from math import pi
# 函数调用
[str(round(pi, i)) for i in range(1, 6)]
# ['3.1', '3.14', '3.142', '3.1416', '3.14159']
# 方法调用
fresh = [' banana', ' loganberry ', 'passion fruit ']
[fruit.strip() for fruit in fresh]
# ['banana', 'loganberry', 'passion fruit']
# 创建元组(必须加括号)
[(x, x ** 2) for x in range(6)]
# [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
注意:当表达式是元组时,圆括号不可省略。[x, x**2 for x in range(6)] 会触发 SyntaxError,因为逗号会被解析为列表分隔符而非元组构造符。
带 if 过滤
在 for 子句后添加 if condition,可以筛选满足条件的元素:
vec = [-4, -2, 0, 2, 4]
# 过滤负数,只保留非负
[x for x in vec if x >= 0] # [0, 2, 4]
# 过滤后转换
[abs(x) for x in vec if x < 0] # [4, 2]
if 子句在表达式求值之前执行。多个条件可以链式书写:
nums = range(20)
[x for x in nums if x % 2 == 0 if x % 3 == 0] # [0, 6, 12, 18]
# 等价于 if x % 2 == 0 and x % 3 == 0
多重 for 子句
列表推导式支持多个 for 子句,实现嵌套循环的效果。执行顺序与嵌套 for 循环一致,从左到右依次嵌套:
# 嵌套循环的等价写法
result = []
for x in [1, 2, 3]:
for y in [3, 1, 4]:
if x != y:
result.append((x, y))
# 推导式写法
[(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
多重 for 常用于展平嵌套列表:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
推导式中 for 的顺序必须与等价嵌套循环完全一致,否则逻辑和结果都会改变。
嵌套推导式
推导式的表达式部分本身可以是另一个列表推导式,常用于处理多维数据:
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
# 转置矩阵
[[row[i] for row in matrix] for i in range(4)]
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
嵌套推导式的执行顺序需要仔细理解:外层 for i in range(4) 控制内层推导式的执行次数,每次执行时内层独立求值。实际应用中,矩阵转置更推荐 zip(*matrix):
list(zip(*matrix))
# [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
与 map/filter 对比
在 Python 3 中,列表推导式在大多数情况下已取代 map() 和 filter():
# map 写法
squares = list(map(lambda x: x ** 2, range(10)))
# 推导式写法
squares = [x ** 2 for x in range(10)]
推导式可读性更强,且天然支持过滤和嵌套。map 配合 filter 需要嵌套调用,可读性急剧下降:
# map + filter 嵌套
list(map(lambda x: x ** 2, filter(lambda x: x > 0, vec)))
# 推导式一行搞定
[x ** 2 for x in vec if x > 0]
不过,当映射函数已经存在且无需 lambda 时,map 依然简洁:list(map(str, nums)) 与 [str(x) for x in nums] 各有千秋。
注意事项
Python 3 中,列表推导式拥有自己独立的局部作用域,循环变量不会泄漏到外部:
x = 'before'
[x ** 2 for x in range(3)]
print(x) # 'before',x 未被覆盖
推导式虽好,但不宜过度嵌套。超过两层嵌套的推导式会严重损害可读性,此时应改用普通循环或提取为函数。