写在最前
在 Python 中,我们经常需要从一组已有数据生成一组新数据。最直观的写法是创建一个空容器,再通过 for 循环逐个处理;而推导式可以把“遍历、处理、筛选”浓缩成一个表达式。
推导式很简洁,但简洁不等于越短越好。它最适合表达逻辑清楚、步骤不多的数据转换。一旦条件开始嵌套,普通循环往往更容易阅读。
1. 从普通循环开始
假设我们要把一组温度值从摄氏度转换成华氏度:
celsius = [0, 10, 20, 30]
fahrenheit = []
for value in celsius:
fahrenheit.append(value * 9 / 5 + 32)
print(fahrenheit)输出:
[32.0, 50.0, 68.0, 86.0]这段代码可以改写为列表推导式:
celsius = [0, 10, 20, 30]
fahrenheit = [value * 9 / 5 + 32 for value in celsius]
print(fahrenheit)可以把它从右向左理解:
[要放入新列表的值 for 当前元素 in 原始数据]也就是:从 celsius 依次取出 value,完成换算,然后把结果放进新列表。
2. 列表推导式
列表推导式最常用的形式有两种。
只转换数据:
[表达式 for 变量 in 可迭代对象]转换数据并进行筛选:
[表达式 for 变量 in 可迭代对象 if 条件]这里的“可迭代对象”可以是列表、元组、字符串、range(),也可以是其他能够被 for 遍历的对象。
2.1 转换每个元素
把商品价格统一保留两位小数:
prices = [12, 8.5, 19.999, 4.2]
formatted_prices = [f"¥{price:.2f}" for price in prices]
print(formatted_prices)输出:
['¥12.00', '¥8.50', '¥20.00', '¥4.20']2.2 使用 if 筛选元素
从成绩中挑出及格的分数:
scores = [58, 91, 76, 43, 85, 60]
passed = [score for score in scores if score >= 60]
print(passed)输出:
[91, 76, 85, 60]末尾的 if 决定一个元素是否进入新列表,它起到的是“过滤”作用。
2.3 if...else 改变元素的结果
如果希望每个元素都被保留,只是根据条件得到不同结果,需要把 if...else 写在 for 前面:
scores = [58, 91, 76, 43, 85, 60]
labels = ["及格" if score >= 60 else "不及格" for score in scores]
print(labels)输出:
['不及格', '及格', '及格', '不及格', '及格', '及格']这两种写法很容易混淆:
# 过滤:不符合条件的元素不会出现在结果中
[score for score in scores if score >= 60]
# 二选一:每个元素都会产生一个结果
["及格" if score >= 60 else "不及格" for score in scores]3. 多层循环
推导式也可以包含多个 for。例如,把二维列表展开成一维列表:
rows = [["A1", "A2"], ["B1", "B2"], ["C1", "C2"]]
cells = [cell for row in rows for cell in row]
print(cells)输出:
['A1', 'A2', 'B1', 'B2', 'C1', 'C2']它对应的普通循环是:
cells = []
for row in rows:
for cell in row:
cells.append(cell)推导式中的循环顺序与普通循环保持一致。不过,当多层循环还夹杂多个条件时,代码会迅速变得难读,这时就应该改回普通循环。
4. 字典推导式
字典推导式同时生成键和值,基本格式为:
{键表达式: 值表达式 for 变量 in 可迭代对象}例如,根据文件名建立“文件名—扩展名”映射:
files = ["notes.md", "avatar.png", "main.py"]
extensions = {
filename: filename.rsplit(".", 1)[-1]
for filename in files
}
print(extensions)输出:
{'notes.md': 'md', 'avatar.png': 'png', 'main.py': 'py'}字典推导式也能添加筛选条件。下面只保留库存大于零的商品:
stock = {
"keyboard": 3,
"mouse": 0,
"monitor": 2,
"cable": 0,
}
available = {
product: quantity
for product, quantity in stock.items()
if quantity > 0
}
print(available)输出:
{'keyboard': 3, 'monitor': 2}需要注意:字典的键不能重复。如果推导过程中产生相同的键,后生成的值会覆盖先前的值。
5. 集合推导式
集合推导式使用花括号,但其中没有 键: 值,基本格式为:
{表达式 for 变量 in 可迭代对象}它很适合在转换数据的同时去重。例如,统计日志中出现过哪些级别:
logs = ["INFO", "ERROR", "info", "WARNING", "error", "INFO"]
levels = {level.lower() for level in logs}
print(levels)结果中只会保留不重复的值:
{'info', 'error', 'warning'}集合是无序的,因此不同环境中打印出来的排列顺序可能不同。如果需要固定顺序,可以在使用时调用 sorted(levels)。
6. 圆括号生成的不是元组,而是生成器
下面的代码看起来像“元组推导式”,但它实际创建的是生成器表达式:
squares = (number ** 2 for number in range(1, 6))
print(squares)
print(next(squares))
print(next(squares))输出类似:
<generator object <genexpr> at 0x...>
1
4列表推导式会立即计算全部结果;生成器则在需要时才计算下一个值。这种方式叫作惰性计算,在处理大量数据时可以减少内存占用。
可以遍历生成器:
for square in squares:
print(square)也可以把它转换成元组:
squares = (number ** 2 for number in range(1, 6))
result = tuple(squares)
print(result)输出:
(1, 4, 9, 16, 25)生成器通常只能完整遍历一次。数据被取出后,它不会自动回到开头;如果需要再次使用,应重新创建生成器。
7. 推导式中的变量作用域
在 Python 3 中,推导式内部的循环变量拥有自己的局部作用域,不会覆盖外部的同名变量:
item = "外部变量"
lengths = [len(item) for item in ["Python", "Go", "Rust"]]
print(lengths)
print(item)输出:
[6, 2, 4]
外部变量即便如此,仍然建议为变量选择有意义的名字。price、filename 通常比 x、i 更容易理解。
8. 一个综合例子
假设从接口得到一组文章数据,我们希望提取所有已发布文章的标题,并清理标题两端的空格:
posts = [
{"title": " Python 虚拟环境 ", "published": True},
{"title": " Git 分支管理", "published": False},
{"title": "列表推导式入门 ", "published": True},
]
published_titles = [
post["title"].strip()
for post in posts
if post["published"]
]
print(published_titles)输出:
['Python 虚拟环境', '列表推导式入门']这段推导式同时完成了三件事:
- 使用
for遍历文章; - 使用末尾的
if筛选已发布文章; - 使用
strip()清理标题并生成新列表。
它仍然可以一眼看懂,因此适合使用推导式。
9. 什么时候不该使用推导式
推导式的目标是让简单的数据处理更清楚,而不是把尽可能多的逻辑塞进一行。
出现下面这些情况时,更推荐普通循环:
- 有多层嵌套条件;
- 每次循环需要执行多个操作;
- 需要记录日志、捕获异常或修改外部状态;
- 为了读懂代码,必须反复确认各部分的执行顺序。
例如,下面的普通循环虽然更长,却更容易扩展和排查问题:
valid_users = []
for user in users:
if not user.get("active"):
continue
email = user.get("email", "").strip().lower()
if not email:
continue
valid_users.append(email)另外,也不建议仅仅为了执行函数而写推导式:
# 不推荐:创建了一个没有实际用途的列表
[print(message) for message in messages]
# 推荐
for message in messages:
print(message)10. 总结
Python 中常见的推导式可以归纳为:
# 列表推导式
[expression for item in iterable if condition]
# 字典推导式
{key_expression: value_expression for item in iterable if condition}
# 集合推导式
{expression for item in iterable if condition}
# 生成器表达式
(expression for item in iterable if condition)方括号创建列表,带有 键: 值 的花括号创建字典,不带键值对的花括号创建集合,而圆括号创建生成器。
真正重要的并不是少写几行代码,而是在简洁和可读性之间找到平衡:简单转换与筛选可以使用推导式,复杂业务逻辑则交给普通循环。