Python 推导式与生成器表达式

它们都属于一种简洁的迭代语法,基本结构是:

1
结果表达式 for 变量 in 可迭代对象 if 条件

其中:

  • for:遍历可迭代对象
  • if:可选,用来筛选元素
  • 最外层符号决定最终生成的对象类型

1. 列表推导式

语法:

1
[表达式 for 变量 in iterable if 条件]

结果是一个 list

1
2
3
4
squares = [x ** 2 for x in range(5)]

print(squares)
# [0, 1, 4, 9, 16]

加入筛选条件:

1
2
3
4
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]

print(even_squares)
# [0, 4, 16, 36, 64]

等价于:

1
2
3
4
5
even_squares = []

for x in range(10):
if x % 2 == 0:
even_squares.append(x ** 2)

列表推导式会立即计算所有元素,并将结果全部保存在内存中。


2. 集合推导式

语法:

1
{表达式 for 变量 in iterable if 条件}

结果是一个 set,因此元素会自动去重。

1
2
3
4
5
6
numbers = [1, 2, 2, 3, 3, 3]

result = {x ** 2 for x in numbers}

print(result)
# {1, 4, 9}

集合是无序的,因此不能依赖输出顺序。


3. 字典推导式

语法:

1
{键表达式: 值表达式 for 变量 in iterable if 条件}

结果是一个 dict

1
2
3
4
squares = {x: x ** 2 for x in range(5)}

print(squares)
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

筛选偶数:

1
2
3
4
5
even_squares = {
x: x ** 2
for x in range(10)
if x % 2 == 0
}

字典的键必须是可哈希对象,并且重复的键会被后面的值覆盖。


4. 生成器表达式

语法:

1
(表达式 for 变量 in iterable if 条件)

结果是一个生成器对象 generator

1
2
3
4
generator = (x ** 2 for x in range(5))

print(generator)
# <generator object ...>

生成器不会立即计算全部结果,而是在需要数据时逐个生成:

1
2
3
4
5
generator = (x ** 2 for x in range(5))

print(next(generator)) # 0
print(next(generator)) # 1
print(next(generator)) # 4

也可以使用循环消费:

1
2
for value in generator:
print(value)

生成器的特点

  1. 惰性计算:用到一个才计算一个。
  2. 节省内存:不会一次性保存所有结果。
  3. 通常只能完整遍历一次。
  4. 适合处理大量数据或数据流。
1
2
3
4
5
6
7
generator = (x for x in range(3))

print(list(generator))
# [0, 1, 2]

print(list(generator))
# []

第二次为空,是因为生成器已经被消费完了。

严格来说,列表、集合和字典形式叫做 comprehension;圆括号形式通常叫做生成器表达式 generator expression,不叫“生成器推导式”。


any()all()

生成器表达式经常和 any()all() 配合使用,因为三者都支持短路计算。

1. any()

语法:

1
any(iterable)

只要可迭代对象中存在一个真值,就返回 True

1
2
3
4
5
6
numbers = [1, 3, 5, 8]

result = any(x % 2 == 0 for x in numbers)

print(result)
# True

含义是:

是否至少存在一个偶数?

逻辑上类似:

1
2
3
4
5
6
for x in numbers:
if x % 2 == 0:
result = True
break
else:
result = False

any() 的短路

一旦找到第一个真值,后面的元素就不会继续检查。

1
2
any([False, False, True, False])
# 遇到 True 后即可结束

空可迭代对象:

1
2
any([])
# False

2. all()

语法:

1
all(iterable)

只有可迭代对象中的所有元素都是真值,才返回 True

1
2
3
4
5
6
numbers = [2, 4, 6, 8]

result = all(x % 2 == 0 for x in numbers)

print(result)
# True

含义是:

是否所有数字都是偶数?

逻辑上类似:

1
2
3
4
5
6
for x in numbers:
if not x % 2 == 0:
result = False
break
else:
result = True

all() 的短路

一旦遇到第一个假值,后面的元素就不会继续检查。

1
2
all([True, True, False, True])
# 遇到 False 后即可结束

空可迭代对象:

1
2
all([])
# True

这是因为空集合中不存在“不满足条件”的元素,也称为“空真”。


为什么搭配生成器表达式

下面两种写法都能工作:

1
any([x > 100 for x in numbers])
1
any(x > 100 for x in numbers)

但第二种更推荐:

1
any(x > 100 for x in numbers)

原因是:

  • 列表推导式会先计算全部结果,生成一个完整列表。
  • 生成器表达式逐个计算。
  • any() 找到一个 True 就结束。
  • all() 找到一个 False 就结束。
  • 因此生成器表达式通常更节省内存,也可能更快。

例如:

1
2
3
numbers = range(1_000_000)

result = any(x == 3 for x in numbers)

检查到 3 时就会立即停止,不需要处理后面的数字。


常用示例

判断是否存在负数

1
2
3
4
5
6
numbers = [3, 7, -2, 10]

has_negative = any(x < 0 for x in numbers)

print(has_negative)
# True

判断是否全部及格

1
2
3
4
5
6
scores = [80, 65, 90, 72]

passed = all(score >= 60 for score in scores)

print(passed)
# True

判断字符串是否包含数字

1
2
3
4
5
6
text = "Python3"

contains_digit = any(char.isdigit() for char in text)

print(contains_digit)
# True

判断密码是否全部由字母和数字构成

1
2
3
4
5
6
password = "abc123"

is_valid = all(char.isalnum() for char in password)

print(is_valid)
# True

不过这个例子中也可以直接写:

1
is_valid = password.isalnum()

外层符号总结

语法 名称 结果类型
[x for x in iterable] 列表推导式 list
{x for x in iterable} 集合推导式 set
{k: v for ...} 字典推导式 dict
(x for x in iterable) 生成器表达式 generator

记忆方式:

1
2
3
4
[]  → list
{} → set
{:} → dict
() → generator

最后送宝宝一句口诀:

要完整结果,用列表推导式;
要去重,用集合推导式;
要键值映射,用字典推导式;
要省内存、配合 any()all(),优先用生成器表达式。