Skip to content

Python 列表生成式:一行代码顶十行的"造列表神器"

引言:自助餐的两种拿法

想象你在一家自助餐厅,面前有一排菜。你想拿一盘菜,有两种拿法:

  • 笨办法:拿一个空盘子,走过去,看到喜欢的就夹,不喜欢就跳过,来回跑好几趟;
  • 聪明办法:拿一个魔法盘子,心里默念"我要所有荤菜",盘子瞬间自动装满。

Python 的**列表生成式(List Comprehensions)**就是这个"魔法盘子"。它用一行代码就能生成一个列表,替代原来需要三四行的循环。学会它,你的代码会立刻变得简洁优雅,这也是 Python 程序员最爱的"语法糖"之一。


一、为什么需要列表生成式?从"笨办法"说起

1.1 问题的出现

要生成列表 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],可以用 list(range(1, 11)),很简单。

但如果要生成平方数列表 [1×1, 2×2, 3×3, ..., 10×10] 呢?

笨办法:循环 + append

python
>>> L = []
>>> for x in range(1, 11):
...     L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

4 行代码,逻辑分散,还要先创建一个空列表。

1.2 列表生成式的解决方案

python
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

一行搞定!生活化理解:就像对魔法盘子说"我要 1 到 10 每个数的平方",盘子瞬间装满。


二、列表生成式语法详解

2.1 基本结构

python
[表达式 for 变量 in 序列]

解读

  • 表达式:每个元素要变成什么样子(比如 x * x);
  • for 变量 in 序列:从哪个序列里取元素;
  • 整体含义:把序列里的每个元素,按表达式加工后,装进新列表。

2.2 加上筛选条件:if

只想要偶数的平方?加个 if

python
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

生活化理解:就像对魔法盘子说"我要 1 到 10 中偶数的平方",盘子自动跳过奇数。

2.3 两层循环:全排列

python
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

执行顺序:外层 for m in 'ABC' 先跑,每取一个 m,内层 for n in 'XYZ' 完整跑一遍。

生活化理解:就像搭配衣服——3 件上衣 × 3 条裤子,每件上衣都和每条裤子试一遍,共 9 种组合。

注意:三层及以上循环虽然语法支持,但可读性会急剧下降,实际中很少用到。


三、实战应用场景

3.1 列出目录内容

python
>>> import os
>>> [d for d in os.listdir('.')]   # 列出当前目录所有文件和文件夹
['.emacs.d', '.ssh', 'Desktop', 'Documents', ...]

os.listdir('.') 返回文件名列表,一行生成式直接变成可用列表。

3.2 批量处理字符串

python
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生活化理解:就像把一堆大小不一的箱子,统一换成标准小号包装。

3.3 字典转列表

python
>>> d = {'x': 'A', 'y': 'B', 'z': 'C'}
>>> [k + '=' + v for k, v in d.items()]
['x=A', 'y=B', 'z=C']

for 循环可以同时迭代多个变量,列表生成式也一样。

3.4 生成坐标点

python
>>> [(x, y) for x in range(3) for y in range(3)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

瞬间生成 3×3 网格的所有坐标,做游戏、画表格时非常实用。


四、if...else 的坑:位置决定命运

列表生成式中最容易搞混的就是 ifelse 的位置。

4.1 情况一:if 在 for 后面(筛选条件)

python
>>> [x for x in range(1, 11) if x % 2 == 0]
[2, 4, 6, 8, 10]

这里的 if过滤器,决定"要不要这个元素"。不能加 else,因为过滤逻辑里"不要"就是直接扔掉,不需要替代品。

python
# 错误写法:for 后面的 if 不能带 else
[x for x in range(1, 11) if x % 2 == 0 else 0]   # SyntaxError!

4.2 情况二:if 在 for 前面(条件表达式)

python
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

这里的 if...else二选一表达式,决定"这个元素变成什么样"。必须加 else,因为每个元素都必须有明确的输出值。

python
# 错误写法:for 前面的 if 必须带 else
[x if x % 2 == 0 for x in range(1, 11)]   # SyntaxError!

4.3 记忆口诀

位置作用能否省略 else类比
iffor 筛选:要不要这个元素不能带 else安检门:不合格的直接拦下
iffor 加工:元素变成什么样必须带 else红绿灯:不是红灯就是绿灯,必须有个结果

一句话总结for 后面管"收不收",for 前面管"收进来后怎么变"。


五、经典练习:安全地转小写

题目:列表中既有字符串又有其他类型,直接转小写会报错,如何用列表生成式安全处理?

python
>>> L = ['Hello', 'World', 18, 'Apple', None]
>>> [s.lower() for s in L]
Traceback (most recent call last):
  ...
AttributeError: 'int' object has no attribute 'lower'

解决方案:用 isinstance 判断类型,只处理字符串

python
>>> L1 = ['Hello', 'World', 18, 'Apple', None]
>>> L2 = [s.lower() for s in L1 if isinstance(s, str)]
>>> print(L2)
['hello', 'world', 'apple']

验证

python
if L2 == ['hello', 'world', 'apple']:
    print('测试通过!')
else:
    print('测试失败!')

思路解析if isinstance(s, str)for 后面,是筛选条件——不是字符串的直接跳过,不会进入 s.lower() 环节。


六、常见误区与避坑指南

误区 1:把列表生成式当万能药,嵌套太深

python
# 可读性极差的写法
[x for x in [y for y in range(10) if y % 2 == 0] if x > 4]

建议:超过两层的嵌套,或者逻辑复杂的转换,老老实实写普通 for 循环。列表生成式的优势是简洁,不是炫技。

误区 2:在生成式里写副作用代码

python
# 不推荐:在生成式里打印
[print(x) for x in range(5)]   # 会生成 [None, None, None, None, None]

列表生成式是用来生成列表的,不是用来执行操作的。如果要做打印、写文件等副作用操作,请用普通循环。

误区 3:混淆 if 的两个位置

python
# 想要"偶数保留,奇数变 0"
[x if x % 2 == 0 else 0 for x in range(5)]   # 正确:[0, 1, 2, 3, 4] → [0, 1, 2, 0, 4]

# 误写成
[x if x % 2 == 0 for x in range(5)]        # 报错!缺 else
[x for x in range(5) if x % 2 == 0 else 0] # 报错!for 后不能带 else

再次默念口诀:for 后筛选不带 else,for 前加工必须带 else

误区 4:生成式里修改原列表

python
L = [1, 2, 3, 4, 5]
# 危险:边生成边修改
L = [x * 2 for x in L if x != 3]   # 其实没问题,因为是先算右边再赋值
# 但如果是下面这样就危险了
for x in L:
    L.append(x * 2)   # 无限循环!

列表生成式本身是先算完再赋值,相对安全;但普通循环中修改原列表是经典大坑。

误区 5:过度使用导致内存爆炸

python
# 危险:一次性生成超大列表
big_list = [x * x for x in range(100000000)]   # 内存可能直接爆掉

如果数据量巨大,应该用生成器表达式(下一篇会讲):(x * x for x in range(...)),它不会一次性生成所有元素,而是边算边给。


七、动手练习

  1. 基础题:用列表生成式生成 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
  2. 进阶题:用列表生成式把 ['a', 'b', 'c'][1, 2, 3] 组合成 ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']
  3. 挑战题:用一行列表生成式找出 1~100 中所有既能被 3 整除又能被 5 整除的数。
  4. 思考题[x for x in range(5)]list(range(5)) 结果一样,它们有什么区别?什么场景下用哪个?

小结

  • 列表生成式[表达式 for 变量 in 序列],一行代码生成列表,替代繁琐的循环。
  • 筛选for 后面加 if 条件,如 [x for x in range(10) if x % 2 == 0]
  • 多层循环for 可以嵌套,生成全排列,如 [m + n for m in 'ABC' for n in 'XYZ']
  • if...else 位置for 后是筛选(不带 else),for 前是加工(必须带 else)。
  • 核心心法:列表生成式是"声明式"编程——你描述"想要什么",而不是"怎么一步步做"。代码更短、更清晰、更接近自然语言。