Python 推导式 (Comprehensions) 是一种简洁、优雅的语法糖 (Syntactic Sugar),它允许我们以一行代码的形式创建列表、字典、集合和生成器。推导式是 Python 语言的一大特色,它能够显著提高代码的可读性和执行效率,是 Pythonic 编程风格的重要组成部分。

核心思想:推导式提供了一种声明式的方式来生成序列,通过将 for 循环和 if 条件语句内联到数据结构(列表、字典、集合)的创建中,从而避免了冗长的传统循环结构,使代码更加紧凑和富有表达力。


一、为什么使用推导式?

在没有推导式之前,我们需要使用传统的 for 循环来创建新的列表、字典或集合。例如,创建一个包含平方数的列表:

传统 for 循环:

1
2
3
4
squares = []
for i in range(10):
squares.append(i * i)
print(squares) # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

使用列表推导式 (List Comprehension),同样的操作可以简化为一行:

1
2
squares = [i * i for i in range(10)]
print(squares) # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

推导式的优势:

  1. 代码简洁性:用更少的代码表达相同的逻辑,减少了模板代码。
  2. 可读性增强:通常更容易理解其意图,因为它将操作和数据结构创建紧密关联。
  3. 性能优化:Python 解释器对推导式进行了优化,通常比同等的 for 循环(特别是循环内频繁执行 append)更快。这主要是因为推导式在内部避免了多次函数调用和 append 操作的开销,通常会预先分配内存。

二、列表推导式 (List Comprehensions)

列表推导式用于快速创建列表。其基本语法结构如下:

[expression for item in iterable if condition]

  • expression:对 item 进行操作的表达式,作为新列表中的元素。
  • item:从 iterable 中取出的每个元素。
  • iterable:一个可迭代对象(如列表、元组、字符串、range 等)。
  • condition (可选):一个布尔表达式,用于过滤 iterable 中的元素。只有当条件为 True 时,item 才会被用于 expression

2.1 1. 基本用法

创建一个平方数的列表:

1
2
3
# [0, 1, 4, 9, ..., 81]
squares = [x * x for x in range(10)]
print(squares)

2.2 2. 带条件过滤

创建一个只包含偶数平方的列表:

1
2
3
# [0, 4, 16, 36, 64]
even_squares = [x * x for x in range(10) if x % 2 == 0]
print(even_squares)

2.3 3. 嵌套循环

生成一个九九乘法表(矩阵形式的元组列表):

1
2
3
4
5
6
7
8
9
# [(1, 1), (1, 2), ..., (9, 9)]
multiplication_table = [(i, j, i * j) for i in range(1, 10) for j in range(1, 10)]
print(multiplication_table[:5]) # 打印前5个元素
# Output: [(1, 1, 1), (1, 2, 2), (1, 3, 3), (1, 4, 4), (1, 5, 5)]

# 将2D列表展平为1D列表
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened_list = [num for row in matrix for num in row]
print(flattened_list) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

2.4 4. 与函数结合使用

对列表中的每个字符串进行大写转换:

1
2
3
words = ["hello", "world", "python"]
upper_words = [word.upper() for word in words]
print(upper_words) # Output: ['HELLO', 'WORLD', 'PYTHON']

三、字典推导式 (Dictionary Comprehensions)

字典推导式用于快速创建字典。其基本语法结构如下:

{key_expression: value_expression for item in iterable if condition}

  • key_expression:作为字典的键。
  • value_expression:作为字典的值。
  • 其余部分与列表推导式相同。

3.1 1. 基本用法

创建一个以数字为键,其平方为值的字典:

1
2
3
# {0: 0, 1: 1, 2: 4, ..., 9: 81}
squares_dict = {x: x * x for x in range(10)}
print(squares_dict)

3.2 2. 带条件过滤

创建一个只包含偶数为键,其平方为值的字典:

1
2
3
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
even_squares_dict = {x: x * x for x in range(10) if x % 2 == 0}
print(even_squares_dict)

3.3 3. 交换键值对

交换字典的键和值(注意:值必须是唯一的才能作为新字典的键):

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}
swapped_dict = {v: k for k, v in my_dict.items()}
print(swapped_dict) # Output: {1: 'a', 2: 'b', 3: 'c'}

四、集合推导式 (Set Comprehensions)

集合推导式用于快速创建集合。集合的特性是元素唯一且无序。其基本语法结构如下:

{expression for item in iterable if condition}

  • 与列表推导式的语法类似,但是使用花括号 {}

4.1 1. 基本用法

创建一个包含平方数的集合:

1
2
3
# {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
squares_set = {x * x for x in range(10)}
print(squares_set)

4.2 2. 自动去重

利用集合的自动去重特性:

1
2
3
numbers = [1, 2, 2, 3, 4, 4, 5]
unique_numbers = {x for x in numbers}
print(unique_numbers) # Output: {1, 2, 3, 4, 5} (顺序可能不同)

4.3 3. 带条件过滤

创建一个包含奇数的集合:

1
2
3
# {1, 3, 5, 7, 9}
odd_numbers = {x for x in range(10) if x % 2 != 0}
print(odd_numbers)

五、生成器推导式 (Generator Expressions)

生成器推导式(也称为生成器表达式)与列表推导式非常相似,但它返回的不是一个列表,而是一个生成器对象 (generator object)。生成器对象是惰性求值 (lazy evaluation) 的,它不会一次性生成所有元素并存储在内存中,而是在迭代时逐个生成元素。

其基本语法结构如下:

(expression for item in iterable if condition)

  • 使用圆括号 () 而不是方括号 [] 或花括号 {}

5.1 1. 基本用法

创建一个平方数的生成器:

1
2
3
4
5
6
7
8
9
10
11
12
# object <generator object <genexpr> at 0x...>
squares_generator = (x * x for x in range(10))
print(squares_generator)

# 迭代生成器
for s in squares_generator:
print(s, end=" ") # Output: 0 1 4 9 16 25 36 49 64 81
print()

# 生成器只能迭代一次
for s in squares_generator:
print(s, end=" ") # 没有任何输出

5.2 2. 内存效率

生成器推导式的最大优势在于内存效率,特别适用于处理大量数据无限序列

列表推导式 vs. 生成器推导式 内存对比:

1
2
3
4
5
6
7
8
9
10
11
import sys

# 列表推导式:一次性生成所有元素并存储
list_comp = [i * i for i in range(1000000)]
print(f"List comprehension size: {sys.getsizeof(list_comp)} bytes")
# Output: List comprehension size: 8000056 bytes (随 Python 版本和元素不同而异)

# 生成器推导式:只存储生成器对象本身,不存储所有元素
gen_comp = (i * i for i in range(1000000))
print(f"Generator comprehension size: {sys.getsizeof(gen_comp)} bytes")
# Output: Generator comprehension size: 112 bytes (一个固定的小尺寸)

5.3 3. 作为函数参数

生成器推导式在作为函数参数传递时,可以省略最外层的圆括号。

1
2
3
4
5
6
7
8
9
10
# 计算所有偶数的平方和
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 方式一:显式创建生成器,再传给 sum()
total_sum = sum((x * x for x in numbers if x % 2 == 0))
print(total_sum) # Output: 220 (2^2+4^2+6^2+8^2+10^2 = 4+16+36+64+100 = 220)

# 方式二:省略圆括号,直接作为参数
total_sum_short = sum(x * x for x in numbers if x % 2 == 0)
print(total_sum_short) # Output: 220

六、多重 forif 子句的顺序

在推导式中,for 子句和 if 子句的顺序与嵌套的循环语句是保持一致的。

[expression for item1 in iterable1 if condition1 for item2 in iterable2 if condition2 ...]

这等价于:

1
2
3
4
5
6
7
result = []
for item1 in iterable1:
if condition1:
for item2 in iterable2:
if condition2:
# ...
result.append(expression)

示例:找出所有三位数中,百位、十位、个位数字之和为偶数的数。
(为了简化,这里只找两位数,和为偶数)

1
2
3
4
5
6
7
8
9
10
11
# 传统嵌套循环
result_loop = []
for i in range(1, 10): # 十位
for j in range(10): # 个位
if (i + j) % 2 == 0:
result_loop.append(i * 10 + j)
print(result_loop[:10]) # Output: [11, 13, 15, 17, 19, 20, 22, 24, 26, 28]

# 列表推导式
result_comp = [i * 10 + j for i in range(1, 10) for j in range(10) if (i + j) % 2 == 0]
print(result_comp[:10]) # Output: [11, 13, 15, 17, 19, 20, 22, 24, 26, 28]

可以看到,推导式中的 for i ... for j ... if ... 顺序与传统循环中的嵌套顺序完全一致。条件 if 作用于它之前的 for 循环。

七、何时不使用推导式?

尽管推导式有很多优点,但并非所有情况都适用。

  1. 逻辑过于复杂:如果表达式、条件或嵌套循环过多过复杂,导致一行代码难以理解,那么传统的 for 循环可能是更好的选择。可读性是第一位的。
  2. 副作用:推导式应主要用于纯粹的转换和过滤,而不应在 expressioncondition 中包含有副作用(如修改外部变量)的代码。这会使代码难以追踪和调试。
  3. 调试困难:对于特别复杂的推导式,其错误信息可能不如多行循环清晰。

反例 (不推荐):

1
2
3
4
5
# 在推导式中引入副作用,不推荐
data = []
result = [data.append(i) for i in range(5)] # data会被修改,但result是[None, None,...]
print(data) # Output: [0, 1, 2, 3, 4]
print(result) # Output: [None, None, None, None, None]

八、总结

Python 推导式是其语言的核心特性之一,提供了创建列表、字典、集合和生成器的简洁高效方式。

  • 列表推导式 []:用于创建列表,最常用。
  • 字典推导式 {key: value}:用于创建字典。
  • 集合推导式 {}:用于创建集合,自动去重。
  • 生成器推导式 ():返回生成器对象,惰性求值,内存效率高,适用于处理大数据流。

熟练掌握推导式能够让你的 Python 代码更加专业、简洁和高效,体现真正的 Pythonic 风格。在编写代码时,考虑是否可以使用推导式来优化你的循环构造,这将是提升你 Python 编程能力的关键一步。