简介
在上一章中,我们学习了 Python 中所有可用的列表方法。很多时候,我们需要用某个特定元素初始化一个列表,或者完整地构建一个列表。
例如,假设应用程序中需要一个 3×4 的矩阵,且所有元素都是 1。用最基本的方式创建这样的列表,我们会使用 for 循环,如下所示:
matrix = []
for i in range(3):
matrix.append([])
for j in range(4):
matrix[i].append(1)
正如你所见,这段代码对于如此简单的用途来说太冗长了。幸运的是,Python 提供了一种更简洁的方式——列表推导式(list comprehension)。
简单的列表推导式
首先,列表推导式本质上只是对上述循环代码的一种语法糖。它允许我们以非常紧凑的方式定义列表。
在内部,Python 会将列表推导式转换为与上面类似的 for 循环语法。
简单列表推导式的一般形式如下:
[expression for var in iterable]
首先是表达式 expression,然后是一个 for 子句,用于遍历某个可迭代对象(iterable)。在每次迭代中,expression 都会被求值,并加入到正在创建的列表中。
标识符 var 在 expression 中是可用的。
如果将上述一般形式赋值给变量 l,即:
l = [expression for var in iterable]
那么它等价于以下代码:
l = []
for var in iterable:
l.append(expression)
快速示例
假设我们要创建一个包含 10 个 0 的列表。使用列表推导式,可以这样写:
nums = [0 for i in range(10)]
如前所述,无论提供什么表达式,都会被求值后放入新列表。在这个例子中,表达式 0 求值为 0,因此每次迭代添加到列表中的都是 0。
再举一个例子:
假设我们要创建一个包含前 10 个平方数(从 0 开始)的列表。使用列表推导式,可以这样定义:
[i ** 2 for i in range(10)]
在 for 循环的每次迭代中(i 从 0 到 9),i ** 2 被求值并追加到正在创建的列表中。
- 第一次迭代:
i = 0→0 ** 2 = 0 - 第二次迭代:
i = 1→1 ** 2 = 1 - 第三次迭代:
i = 2→2 ** 2 = 4 - ...
- 第十次迭代:
i = 9→9 ** 2 = 81
最终结果为:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
任何合法的表达式都可以用在列表推导式中,包括函数调用、运算符,甚至条件表达式。
条件表达式(Conditional Expressions)
条件表达式会先判断一个条件,如果为 True 就返回一个值,否则返回另一个值。
我们可以在列表推导式中使用条件表达式,使其更加灵活。
例如,假设我们要创建一个包含 10 个数字的列表,从 0 开始,交替为 0 和 1。比如,4 个数字时应为 [0, 1, 0, 1]。
我们可以利用条件表达式实现:
[0 if i % 2 == 0 else 1 for i in range(10)]
# 结果: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
在每次迭代中,条件表达式 0 if i % 2 == 0 else 1 被求值,并将结果加入列表。
- 当
i = 0(能被 2 整除)→ 返回 0 - 当
i = 1(不能被 2 整除)→ 返回 1 - 当
i = 2→ 返回 0 - ……以此类推
再举一个例子:
假设我们要创建一个包含前 10 个整数(从 0 开始)的列表,其中能被 3 整除的位置替换为 -1。
期望结果:[-1, 1, 2, -1, 4, 5, -1, 7, 8, -1]
实现如下:
[-1 if i % 3 == 0 else i for i in range(10)]
# 结果: [-1, 1, 2, -1, 4, 5, -1, 7, 8, -1]
如果 i % 3 == 0 为真(即能被 3 整除),就放入 -1;否则放入 i 本身。
练习题:
要创建一个包含前 15 个整数(从 0 开始)的列表,其中第 n 个元素:
- 如果该整数能被 4 整除,则为其平方;
- 否则为 0。
请用列表推导式构造这个列表。
✅ 答案:
[i ** 2 if i % 4 == 0 else 0 for i in range(15)]
函数调用
函数调用也是一种表达式,因此也可以合法地用在列表推导式中。我们可以将推导式中 for 循环的变量传递给函数,由函数进行相应处理。
示例 1
假设我们要从一个字符串创建一个列表,其中每个元素是原字符串对应字符的大写形式,并前后各加一个空格。例如,字符串 'Bye' 应转换为 [' B ', ' Y ', ' E ']。
使用字符串的 .upper() 方法,可以这样实现:
s = 'Bye'
l = [' ' + char.upper() + ' ' for char in s]
print(l) # [' B ', ' Y ', ' E ']
for char in s 遍历字符串中的每个字符,并赋值给 char。在推导式的表达式中,char 被转为大写,并在两侧加上空格。
示例 2:阶乘列表
假设我们要创建一个包含前 10 个阶乘数(从 0! 开始)的列表。
阶乘定义:n! = n × (n-1) × ... × 1,且 0! = 1。
我们先定义一个 factorial() 函数,再在列表推导式中调用它:
def factorial(n):
num = 1
for i in range(1, n + 1):
num *= i
return num
factorial_nums = [factorial(i) for i in range(10)]
print(factorial_nums)
# 输出: [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
注:Python 的
math模块也提供了math.factorial()函数,可以直接使用。这里我们自定义函数是为了演示如何在推导式中调用函数。
更多过滤(Filtering with if)
前面我们使用条件表达式在两个值之间选择。但在某些情况下,我们只想在满足某个条件时才将元素加入列表;如果不满足,就跳过该次迭代。
这时,仅靠条件表达式不够,因为条件表达式总会返回一个值(要么 if 分支,要么 else 分支)。
解决方法是:在 for 子句后添加一个 if 条件子句。
一般形式如下:
[expression for var in iterable if condition]
只有当 condition 为 True 时,expression 才会被求值并加入列表。
如果我们将此赋值给变量 l:
l = [expression for var in iterable if condition]
它等价于:
l = []
for var in iterable:
if condition:
l.append(expression)
注意:append() 语句位于 if 内部,说明只有条件满足时才修改列表。
示例 1:前 10 个非负偶数
evens = [i for i in range(20) if i % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
提示:这种列表也可以用
list(range(0, 20, 2))实现,但列表推导式更通用。
示例 2:前 10 个正偶数的立方
even_cubes = [i ** 3 for i in range(2, 22) if i % 2 == 0]
print(even_cubes)
# [8, 64, 216, 512, 1000, 1728, 2744, 4096, 5832, 8000]
这里我们遍历 2 到 21(不含),只保留偶数,并计算其立方。
嵌套循环(Nested Loops)
并非总是只需要一维列表。很多时候我们需要二维甚至三维列表。
我们已经知道如何用推导式创建简单列表。Python 的列表推导式也支持创建多维列表。
就像我们可以嵌套 for 循环:
for var1 in iterable1:
for var2 in iterable2:
pass
在推导式中,也可以将嵌套的 for 子句依次写出:
[expression for var1 in iterable1 for var2 in iterable2]
这等价于:
l = []
for var1 in iterable1:
for var2 in iterable2:
l.append(expression)
示例:坐标列表
创建一个 x 从 0 到 5、y 从 0 到 4 的所有坐标点:
coordinates = [(i, j) for i in range(6) for j in range(5)]
print(coordinates)
# [(0,0), (0,1), ..., (5,4)]
注意:range(6) 是因为要包含 5,而 range(5) 包含 0~4。
表达式 (i, j) 中同时使用了 i 和 j,因为表达式是在所有 for 子句至少执行一次后才求值的。
三重嵌套示例
创建 x∈[0,2], y∈[0,2], z∈[0,1] 的所有三维坐标:
coordinates = [(i, j, k) for i in range(3) for j in range(3) for k in range(2)]
print(coordinates)
# [(0,0,0), (0,0,1), (0,1,0), ..., (2,2,1)]
一般来说,for 子句越靠后,嵌套层级越深。
二维列表(2D Lists)
列表推导式非常灵活,可用于创建各种类型的列表,尤其是列表的列表(即二维列表)。
思路是:在主循环的每次迭代中,向主列表中添加一个子列表。这个子列表可以手动定义,也可以用另一个推导式生成。
示例 1:3×4 的零矩阵
[[0, 0, 0, 0] for i in range(3)]
# [[0,0,0,0], [0,0,0,0], [0,0,0,0]]
每次迭代都把 [0,0,0,0] 加入主列表,共 3 次。
示例 2:5×30 的零矩阵(需嵌套推导式)
手动写 30 个 0 不现实,所以内层也用推导式:
[[0 for j in range(30)] for i in range(5)]
- 外层
for i in range(5):创建 5 行 - 内层
[0 for j in range(30)]:每行是一个包含 30 个 0 的列表
最终得到一个 5 行、每行 30 列的二维列表(5×30 矩阵),所有元素均为 0。
💡 注意:这种写法是正确创建独立子列表的方式。
错误写法如[[0]*30] * 5会导致所有行引用同一个列表,修改一行会影响所有行!
通过列表推导式,我们可以用极其简洁和高效的方式创建各种复杂结构的列表,大幅提升代码可读性和开发效率。