在 Python 中,推导式(Comprehensions) 是一种简洁、高效地创建序列或映射对象的语法结构。你可以把它理解为:用一段描述性的表达式,直接生成一个完整的容器(列表、集合、字典) 。推导式让代码更紧凑、可读性更高,并且在大多数情况下执行速度比普通的 for 循环更快。
下面我们逐一详细解说三种标准推导式,并澄清"元组推导式"为何实际上是生成器。
1. 列表推导式(List Comprehension)
列表推导式是最常用的一种推导式,用于快速生成新的列表。
基本语法
python
[expression for item in iterable if condition]
expression:对item的运算或直接就是item本身。for item in iterable:迭代任何可迭代对象。if condition(可选):筛选条件,只有条件为True的item才会被处理。
执行逻辑
- 遍历
iterable中的每一个元素。 - 如果提供了
if,则先判断条件;条件为False则跳过。 - 对符合条件的元素计算
expression,将结果放入新列表。
示例
python
# 生成 0~9 的平方列表
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 带条件的:只保留偶数平方
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
# 嵌套循环:生成坐标对
coords = [(x, y) for x in range(2) for y in range(3)]
print(coords) # [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]
# 对字符串列表处理
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words) # ['HELLO', 'WORLD', 'PYTHON']
特点与优势
- 速度快 :列表推导式在 C 语言层面进行循环和追加,避免了 Python 层的
append方法调用开销。 - 可读性强 :将意图集中在一行内,避免冗长的
for循环结构。 - 内存占用:会立即生成完整的列表对象,数据量大时需注意内存消耗。
2. 集合推导式(Set Comprehension)
集合推导式与列表推导式几乎相同,只是用花括号 {} 包裹,生成的是一个集合(set),因此自动去重且无序。
基本语法
python
{expression for item in iterable if condition}
示例
python
# 生成 0~9 的平方集合(自动去重)
squares_set = {x**2 for x in range(-3, 4)} # 负数平方与正数相同
print(squares_set) # {0, 1, 4, 9}
# 从列表中去重并转为大写
names = ["Alice", "bob", "alice", "Bob", "Charlie"]
unique_names = {name.capitalize() for name in names}
print(unique_names) # {'Alice', 'Bob', 'Charlie'}
# 带条件的集合推导式
vowels = {char for char in "hello world" if char in "aeiou"}
print(vowels) # {'e', 'o'}
特点
- 自动去重:集合不允许重复元素。
- 无序性:输出顺序不固定(但 Python 3.7+ 后字典和集合内部顺序是插入顺序,但集合顺序依然可能因哈希随机化而变化)。
- 语法与列表推导式仅括号不同,但意义不同。
3. 字典推导式(Dictionary Comprehension)
字典推导式也使用花括号 {},但必须包含冒号 : 来区分键和值。
基本语法
python
{key_expression: value_expression for item in iterable if condition}
示例
python
# 生成数字及其平方的映射
squares_dict = {x: x**2 for x in range(5)}
print(squares_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 从两个列表构建字典
keys = ["name", "age", "city"]
values = ["Alice", 30, "New York"]
info = {k: v for k, v in zip(keys, values)}
print(info) # {'name': 'Alice', 'age': 30, 'city': 'New York'}
# 带条件的字典推导式
numbers = [1, 2, 3, 4, 5, 6]
even_odd = {n: ("even" if n % 2 == 0 else "odd") for n in numbers}
print(even_odd) # {1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd', 6: 'even'}
# 交换字典的键和值
original = {"a": 1, "b": 2, "c": 3}
swapped = {v: k for k, v in original.items()}
print(swapped) # {1: 'a', 2: 'b', 3: 'c'}
特点
- 键唯一性:如果表达式产生的键重复,后面的值会覆盖前面的值。
- 非常适用于对现有字典进行过滤、转换或重组。
4. "元组推导式" ------ 实际上是生成器表达式
你可能会尝试用圆括号写类似推导式的东西:
python
tup = (x**2 for x in range(5))
print(tup) # <generator object <genexpr> at 0x...>
你会发现得到的不是元组 ,而是一个 generator 对象。这就是为什么 Python 里**没有"元组推导式"**的原因。
生成器表达式(Generator Expression)
- 语法:
(expression for item in iterable if condition) - 它返回一个生成器对象,该对象是惰性求值的:只在需要下一个元素时才计算,不一次性占用大量内存。
- 可以把它当作一种"迭代器工厂",用于函数调用、
for循环或转换为其他容器。
示例
python
# 生成器表达式
gen = (x**2 for x in range(5))
print(next(gen)) # 0
print(next(gen)) # 1
print(list(gen)) # [4, 9, 16] (剩余的元素)
# 常用场景:作为函数参数,避免中间列表
sum_of_squares = sum(x**2 for x in range(10)) # 没有额外的列表内存开销
如何得到一个元组?
如果你想要一个元组,可以将生成器表达式传给 tuple() 构造函数:
python
tup = tuple(x**2 for x in range(5))
print(tup) # (0, 1, 4, 9, 16)
但这不是推导式语法,而是类型转换。
总结对比表
| 类型 | 语法 | 输出类型 | 特点 |
|---|---|---|---|
| 列表推导式 | [exp for ...] |
list |
有序、可重复、立即生成 |
| 集合推导式 | {exp for ...} |
set |
无序、去重、立即生成 |
| 字典推导式 | {k: v for ...} |
dict |
键值对、键唯一、立即生成 |
| 生成器表达式 | (exp for ...) |
generator |
惰性求值、内存友好、只能迭代一次 |
使用建议
- 列表推导式:适用于结果集规模不大、需要随机访问或多次迭代的场景。
- 集合推导式:需要去重且不关心顺序时使用。
- 字典推导式:构建或转换映射关系时非常方便。
- 生成器表达式:处理大规模数据流、或者只需要一次遍历时使用,可以有效节省内存。
推导式是 Python 风格的精华之一,合理使用能让代码更加 Pythonic。