生成式
# Python 生成式
在 Python 中,生成式 (Comprehensions) 是一种极其强大且富有表现力的语法糖。它允许你用一种非常简洁、可读性高的方式,基于一个已有的可迭代对象(如列表、元组等)来创建新的数据结构(如列表、字典、集合)。
# 1. 为什么需要生成式?
在学习生成式之前,让我们先看一个常见的任务:创建一个包含 1 到 10 的平方数的列表。
传统 for
循环写法:
squares = [] # 1. 初始化一个空列表
for x in range(1, 11): # 2. 遍历数字
squares.append(x ** 2) # 3. 将计算结果追加到列表中
print(squares)
# 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
2
3
4
5
6
这个过程需要三行代码。现在,让我们看看列表生成式的写法。
列表生成式写法:
squares = [x ** 2 for x in range(1, 11)]
print(squares)
# 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
2
3
4
一行代码就完成了同样的工作!这就是生成式的魅力:让代码更紧凑、更优雅、意图更清晰。
# 2. 列表生成式 (List Comprehension)
列表生成式用于快速创建一个新的列表。
# 2.1 基本语法
它的语法结构可以分解为几个部分:
new_list = [output_expression for item in iterable]
[]
: 方括号表示我们正在创建一个列表。output_expression
: 输出表达式。对item
进行处理后的结果,它将成为新列表的元素。for item in iterable
: 一个标准的for
循环,用于遍历源可迭代对象。
# 2.2 带 if
条件的列表生成式
我们还可以在生成式中加入 if
语句,用于筛选元素。
语法模板:
new_list = [output_expression for item in iterable if condition]
if condition
: 一个筛选条件。只有当condition
为True
时,output_expression
的结果才会被加入到新列表中。
示例:筛选出10以内的所有偶数并平方
传统
for
循环:even_squares = [] for x in range(1, 11): if x % 2 == 0: even_squares.append(x ** 2) print(even_squares)
1
2
3
4
5列表生成式:
even_squares = [x ** 2 for x in range(1, 11) if x % 2 == 0] print(even_squares)
1
2输出结果 (两者相同):
[4, 16, 36, 64, 100]
1
# 2.3 带 if-else
的列表生成式
如果需要根据条件对输出表达式进行选择(类似于三元运算符),if-else
必须放在 for
循环的前面。
语法模板:
new_list = [output_if_true if condition else output_if_false for item in iterable]
示例:为数字打上“奇数”或“偶数”的标签
labels = ["偶数" if i % 2 == 0 else "奇数" for i in range(1, 6)]
print(labels)
# 输出: ['奇数', '偶数', '奇数', '偶数', '奇数']
2
3
# 2.4 嵌套循环的列表生成式
列表生成式也支持嵌套 for
循环。
示例:生成一个简单的坐标对
- 传统
for
循环:pairs = [] for x in range(1, 3): for y in range(3, 5): pairs.append((x, y)) print(pairs)
1
2
3
4
5 - 列表生成式:
pairs = [(x, y) for x in range(1, 3) for y in range(3, 5)] print(pairs)
1
2 - 输出结果 (两者相同):(注意
[(1, 3), (1, 4), (2, 3), (2, 4)]
1for
循环的嵌套顺序与生成式中的顺序是一致的)
# 3. 字典生成式 (Dictionary Comprehension)
与列表生成式类似,字典生成式用于快速创建字典。
语法
new_dict = {key_expression: value_expression for item in iterable if condition}
{}
: 花括号表示我们正在创建一个字典。key_expression: value_expression
: 定义了新字典的键和值。
示例:创建数字及其平方的字典
squares_dict = {x: x ** 2 for x in range(1, 6)}
print(squares_dict)
# 输出: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
2
3
示例:合并两个列表为一个字典 zip()
函数可以将两个列表的元素一一配对,非常适合用于字典生成式。
keys = ["name", "age", "city"]
values = ["Alice", 30, "New York"]
profile = {k: v for k, v in zip(keys, values)}
print(profile)
# 输出: {'name': 'Alice', 'age': 30, 'city': 'New York'}
2
3
4
5
6
# 4. 集合生成式 (Set Comprehension)
集合生成式在语法上与列表生成式非常相似,但使用花括号 {}
,并且其结果是一个集合,这意味着所有元素都是唯一的。
语法
new_set = {output_expression for item in iterable if condition}
示例:从一个有重复元素的列表中提取所有唯一元素的平方
numbers = [1, 2, 2, 3, 4, 4, 5]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)
# 输出: {1, 4, 9, 16, 25} (重复的2和4被自动去除了)
2
3
4
# 5. 生成器表达式 (Generator Expression)
严格来说,Python 没有“元组生成式”。如果你使用圆括号 ()
,你创建的不是元组,而是一个生成器表达式。
# 5.1 语法与核心概念
语法模板:
generator_object = (output_expression for item in iterable if condition)
- 核心区别:生成器表达式不会立即计算并创建所有元素,而是返回一个生成器对象 (generator object)。
- 惰性求值 (Lazy Evaluation):生成器只在被迭代时(例如,在
for
循环中)才逐个计算和“生成”下一个值。
# 5.2 为什么使用生成器表达式?—— 节省内存
当你处理非常大的数据集时,一次性创建包含所有结果的列表可能会消耗大量内存。生成器则完美地解决了这个问题。
示例:计算十亿个数的和
# 使用列表生成式 (会尝试创建含10亿个整数的列表,可能导致内存耗尽)
# list_sum = sum([i for i in range(1_000_000_000)]) # 不要运行!
# 使用生成器表达式 (几乎不占内存)
generator = (i for i in range(1_000_000_000))
# sum() 函数可以高效地处理生成器,逐个获取数字并累加
# gen_sum = sum(generator) # 运行也需要很长时间,但内存友好
print("生成器已创建,但内部的数字尚未计算。")
2
3
4
5
6
7
8
# 5.3 使用生成器
你可以通过以下方式使用生成器:
- 在
for
循环中迭代。 - 将其传递给可以处理可迭代对象的函数(如
sum()
,list()
,tuple()
,set()
)。
重要提示:一个生成器只能被完整地迭代一次。
gen = (x * 2 for x in range(5))
print("第一次迭代:")
for item in gen:
print(item, end=" ") # 输出: 0 2 4 6 8
print("\n第二次迭代:")
for item in gen:
print(item, end=" ") # 无任何输出,因为生成器已经耗尽
# 如果想得到元组,需要用 tuple() 转换
squares_tuple = tuple(x ** 2 for x in range(1, 6))
print("\n转换后的元组:", squares_tuple)
# 输出: (1, 4, 9, 16, 25)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6. 总结:何时使用生成式?
优点:
- 代码简洁:用一行代码代替多行循环,使代码更紧凑。
- 可读性高:对于简单的逻辑,生成式的意图一目了然。
- 效率:通常比手动
append
的for
循环稍快。 - 内存友好:生成器表达式在处理大数据时有巨大优势。
缺点/注意事项:
- 可读性陷阱:当逻辑变得复杂时(例如,超过两层嵌套循环或复杂的
if
条件),生成式会迅速变得难以阅读。 - 调试困难:在复杂的生成式中定位错误比在多行
for
循环中更难。
- 可读性陷阱:当逻辑变得复杂时(例如,超过两层嵌套循环或复杂的
最佳实践法则:
如果逻辑简单,能用一行清晰地表达,就大胆使用生成式。如果逻辑复杂,需要多层嵌套或复杂的条件分支,那么退回到标准的
for
循环通常是更明智的选择,因为它能提供更好的可读性和可维护性。