跳到主要内容

列表生成式

列表生成式(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. 用列表生成式创建 1 到 20 的平方数列表。
  2. 从一组句子中筛选长度至少为 5 的句子,并去除两端空白。
  3. 将二维列表 [[1, 2], [3, 4]] 展平成 [1, 2, 3, 4]。
  4. 用字典生成式把 ['a', 'bb', 'ccc'] 映射为单词及其长度。
  5. 分别使用列表生成式和生成器表达式计算 range(1_000_000) 的平方和,并比较内存行为。

小结​

  • 列表生成式的核心形式是 [expression for item in iterable]。
  • for 后的 if 用于筛选,if ... else 表达式用于为每个位置选择值。
  • 多个 for 可以处理嵌套数据,但嵌套过深会降低可读性。
  • Python 3 中生成式循环变量有独立作用域,不会覆盖外层同名变量。
  • 列表生成式立即创建列表;生成器表达式惰性地产生值。
  • 生成式适合纯粹的数据转换,复杂异常处理和副作用应使用普通循环。