列表生成式
列表生成式(list comprehension)用一条表达式,根据已有可迭代对象快速创建新列表。它适合把“遍历、转换、筛选”这类简单流程写在一起,同时保留清晰的代码结构。
本文示例适用于 Python 3。学完后,你应该能够:
- 读懂并编写基本列表生成式;
- 正确放置条件筛选和条件表达式;
- 使用多个
for、解包和字典数据; - 判断什么时候应改用普通循环、集合生成式、字典生成式或生成器表达式;
- 避免嵌套过深、变量作用域和可变对象复用等问题。
基本语法
最简单的形式是:
[expression for item in iterable]
例如,把数字平方:
numbers = [1, 2, 3, 4]
squares = [number * number for number in numbers]
print(squares) # [1, 4, 9, 16]
它大致等价于:
squares = []
for number in numbers:
squares.append(number * number)
生成式左侧的 expression 决定新列表元素是什么,for 后面的对象决定数据来源。
加入筛选条件
在 for 后添加 if,只保留满足条件的元素:
numbers = range(10)
even_numbers = [number for number in numbers if number % 2 == 0]
print(even_numbers) # [0, 2, 4, 6, 8]
语法为:
[expression for item in iterable if condition]
执行顺序可以理解为“先取一个元素,判断条件,条件成立后计算表达式并追加结果”。因此,下例不会对负数调用平方根:
import math
values = [-4, 0, 9, 16]
roots = [math.sqrt(value) for value in values if value >= 0]
可以连续写多个 if,但条件太多时应拆成普通循环或命名函数,让意图更明显。
条件表达式和筛选条件不是一回事
想让每个元素都进入结果,但根据条件选择不同值时,使用条件表达式:
labels = ["偶数" if number % 2 == 0 else "奇数" for number in range(5)]
print(labels) # ['偶数', '奇数', '偶数', '奇数', '偶数']
形式是:
[value_if_true if condition else value_if_false for item in iterable]
对比下面两种写法:
[number for number in range(5) if number % 2 == 0] # 只保留偶数
[number if number % 2 == 0 else 0 for number in range(5)] # 保留全部位置
if 放在 for 后是筛选;if ... else 放在表达式中是转换。混淆位置是列表生成式最常见的错误之一。
转换字符串和对象
列表生成式不只用于数字:
names = [" ada ", "LIN", "mo"]
clean_names = [name.strip().title() for name in names]
print(clean_names) # ['Ada', 'Lin', 'Mo']
处理字典时,可以从 items() 解包键和值:
scores = {"Ada": 95, "Lin": 88, "Mo": 91}
passed = [name for name, score in scores.items() if score >= 90]
print(passed) # ['Ada', 'Mo']
如果结果本身应是键值映射,应使用字典生成式,而不是先创建元组列表再转换:
bonus_scores = {name: score + 5 for name, score in scores.items()}
多个 for:处理嵌套数据
多个 for 会按照从左到右的顺序嵌套执行:
colors = ["red", "blue"]
sizes = ["S", "M"]
variants = [f"{color}-{size}" for color in colors for size in sizes]
print(variants)
# ['red-S', 'red-M', 'blue-S', 'blue-M']
它等价于:
variants = []
for color in colors:
for size in sizes:
variants.append(f"{color}-{size}")
常见用途是展平二维列表:
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [value for row in matrix for value in row]
print(flat) # [1, 2, 3, 4, 5, 6]
如果需要三层或更多嵌套,或者每层都有复杂分支,普通循环通常更容易阅读和调试。
生成式中的变量作用域
Python 3 中,列表生成式的循环变量有自己的局部作用域,不会覆盖外层同名变量:
number = "outside"
values = [number for number in range(3)]
print(values) # [0, 1, 2]
print(number) # outside
表达式仍然可以读取外层变量:
tax = 0.1
prices = [100, 200]
with_tax = [price * (1 + tax) for price in prices]
循环变量在生成式结束后不可用,不要把它当作结果变量使用。
解包和条件组合
对由元组组成的数据,可以直接在 for 中解包:
points = [(1, 2), (3, 4), (5, 6)]
distances = [x * x + y * y for x, y in points]
也可以同时转换和筛选:
records = [("Ada", 95), ("Lin", 58), ("Mo", 82)]
report = [f"{name}: {score}" for name, score in records if score >= 60]
当输入项结构不一致时,解包会抛出 ValueError;如果数据不可信,应先验证结构,而不是用复杂表达式掩盖问题。
调用函数和异常处理
表达式中可以调用函数,但应注意函数是否有副作用:
def parse_int(text):
return int(text)
numbers = [parse_int(text) for text in ["10", "20", "30"]]
列表生成式没有单独的 try ... except 语法。遇到可能失败的转换时,可以先写普通循环处理异常:
texts = ["10", "bad", "30"]
numbers = []
for text in texts:
try:
numbers.append(int(text))
except ValueError:
continue
这样能够明确决定如何记录、跳过或报告错误。
列表生成式与其他生成式
集合生成式
集合生成式使用花括号,自动去重:
letters = {char.lower() for char in "Python"}
字典生成式
字典生成式使用 key: value:
lengths = {word: len(word) for word in ["Go", "Python", "Dart"]}
生成器表达式
把方括号改成圆括号会得到生成器表达式:
list_result = [number * 2 for number in range(3)]
generator_result = (number * 2 for number in range(3))
列表生成式会立即创建完整列表,适合需要重复访问或索引的结果;生成器表达式按需产生值,适合一次性处理大数据。不要为了“看起来简短”而默认选择生成器,后续若要多次遍历,它会因为已消费而得到空结果。
性能与可读性
列表生成式通常比手写 append() 的 Python 循环更紧凑,且实现经过优化,但它仍需要为所有结果分配内存。结果长度为 n 时,时间通常为 O(n),空间也约为 O(n)。
values = [value * 2 for value in range(1_000_000)] # 占用较多内存
total = sum(value * 2 for value in range(1_000_000)) # 流式计算
选择标准:
- 结果需要索引、切片或多次使用:列表生成式;
- 只需把结果交给
sum()、any()等消费:生成器表达式; - 逻辑包含多步操作、日志、副作用或异常分支:普通
for循环; - 结果需要去重或键值映射:集合生成式或字典生成式。
可变对象复用陷阱
生成式本身不会复制元素。如果表达式重复引用同一个可变对象,修改其中一项会影响其他项:
row = []
matrix = [row] * 3
matrix[0].append(1)
print(matrix) # [[1], [1], [1]]
需要创建三个独立列表时,使用生成式为每次迭代创建新对象:
matrix = [[] for _ in range(3)]
matrix[0].append(1)
print(matrix) # [[1], [], []]
下划线 _ 表示循环值不会被使用,但它仍然会参与指定次数的迭代。
常见错误
把筛选条件写成条件表达式
# 结果会包含 0,而不是只保留偶数
[number if number % 2 == 0 else 0 for number in range(5)]
只保留元素应把 if 放在 for 后:
[number for number in range(5) if number % 2 == 0]
生成式过于复杂
一行代码同时包含多层 for、多个条件和嵌套函数调用时,读者很难确认执行顺序。拆成有意义的中间变量,通常更容易维护。
在生成式中执行副作用
[print(item) for item in items] # 得到一个充满 None 的列表
生成式应主要用于产生数据;打印、写文件、修改外部状态等副作用请使用普通循环。
练习
- 用列表生成式创建
1到20的平方数列表。 - 从一组句子中筛选长度至少为
5的句子,并去除两端空白。 - 将二维列表
[[1, 2], [3, 4]]展平成[1, 2, 3, 4]。 - 用字典生成式把
['a', 'bb', 'ccc']映射为单词及其长度。 - 分别使用列表生成式和生成器表达式计算
range(1_000_000)的平方和,并比较内存行为。
小结
- 列表生成式的核心形式是
[expression for item in iterable]。 for后的if用于筛选,if ... else表达式用于为每个位置选择值。- 多个
for可以处理嵌套数据,但嵌套过深会降低可读性。 - Python 3 中生成式循环变量有独立作用域,不会覆盖外层同名变量。
- 列表生成式立即创建列表;生成器表达式惰性地产生值。
- 生成式适合纯粹的数据转换,复杂异常处理和副作用应使用普通循环。