Python列表生成式(List Comprehensions)教程

1. 基本概念:一行代码替代循环的「魔法糖」

写Python时,生成列表是最日常不过的操作——从统计平方数、过滤数据到处理文件,处处都有列表的身影。

新手入门时,大概率会先用循环+append的方式写。比如要生成1-10的平方列表:

# 新手常用写法
squares = []
for x in range(1, 11):
    squares.append(x**2)
print(squares)  # 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但如果用列表生成式,一行就能搞定:

# 列表生成式写法
squares = [x**2 for x in range(1, 11)]
print(squares)  # 输出同上

这就是列表生成式的魅力:它是Python内置的「语法糖」,能把「遍历可迭代对象→对元素做转换→加入新列表」的逻辑压缩到一行,既简洁又清晰(前提是别写太复杂😅)。


2. 基本语法拆解

列表生成式的核心结构非常直观,像把循环逻辑「倒过来」写:

[对元素做的操作 for 变量 in 可迭代对象]

比如刚才的平方例子:

  • range(1, 11):可迭代对象,提供1-10的数
  • x:循环变量,逐个接收可迭代对象的元素
  • x**2:对每个x的操作,计算平方
  • []:最后把所有结果包成列表

3. 进阶:带条件的列表生成式

列表生成式不止能做「转换」,还能加过滤更灵活的转换规则

3.1 过滤条件:在for后加if

如果只想保留偶数的平方,不需要在循环里写if判断再append,直接在末尾加过滤条件即可:

even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]
print(even_squares)  # 输出: [4, 16, 36, 64, 100]

⚠️ 关键注意点:这里的if筛选器,只留下符合条件的元素,不能加else——因为不符合的直接丢弃,不需要替代值。


3.2 条件表达式:在for前加if...else

如果需要对每个元素做「二选一」的转换(比如保留偶数,把奇数变负数),就要用Python的三元条件表达式,而且必须放在for的前面:

# 偶数留原值,奇数取负
numbers = [x if x % 2 == 0 else -x for x in range(1, 11)]
print(numbers)  # 输出: [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

⚠️ 关键注意点:这里的if...else转换器的一部分,必须完整(带else)——因为每个元素都要生成一个值,不能丢弃。


4. 多层循环:处理嵌套可迭代对象

列表生成式还能写双重、三重甚至更多层循环,用来生成嵌套结构或者全排列组合这类东西。

4.1 双重循环:生成全排列

# 遍历字母'A'/'B'/'C'和'X'/'Y'/'Z',两两拼接
combinations = [m + n for m in 'ABC' for n in 'XYZ']
print(combinations)  # 输出: ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

循环顺序和你写嵌套for的顺序是一致的:先执行外层for m in 'ABC',再执行内层for n in 'XYZ',等效于:

combinations = []
for m in 'ABC':
    for n in 'XYZ':
        combinations.append(m + n)

4.2 三重循环:生成简单三维坐标

三重及以上循环逻辑会开始变得复杂,除非特别简单,否则不建议用列表生成式——可读性会下降。比如生成2x2x2的立方体坐标点:

points = [(x, y, z) for x in range(2) for y in range(2) for z in range(2)]
print(points)  # 输出: [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]

5. 实际场景小案例

光说不练假把式,看看列表生成式在日常开发中怎么用:

5.1 处理字典:格式化键值对

遍历字典的items(),同时拿到键和值:

d = {'x': 'A', 'y': 'B', 'z': 'C'}
formatted_pairs = [f"{k}={v}" for k, v in d.items()]
print(formatted_pairs)  # 输出: ['x=A', 'y=B', 'z=C']

5.2 简单文件系统操作:筛选.py文件

结合os模块,列出当前目录下所有Python脚本:

import os

# 先列所有文件/目录,再筛选后缀为.py的
py_scripts = [f for f in os.listdir('.') if f.endswith('.py')]
print(py_scripts)

5.3 类型安全处理:只处理字符串

当列表里有混合类型(比如字符串、数字、None)时,用isinstance()先做类型检查,避免调用不存在的方法报错:

mixed_list = ['Hello', 'World', 18, 'Apple', None]
# 只把字符串转小写,其他类型直接丢弃
safe_lower = [s.lower() for s in mixed_list if isinstance(s, str)]
print(safe_lower)  # 输出: ['hello', 'world', 'apple']

6. 性能与内存小提示

6.1 列表生成式 vs 传统循环:谁更快?

通常情况下,列表生成式比传统for+append更快——因为Python解释器可以直接优化列表生成式的底层执行,减少中间字节码的调用次数。


6.2 大数据量怎么办?用生成器表达式!

如果要处理几十万甚至上百万的元素,列表生成式会一次性把所有结果加载到内存里,可能导致内存溢出。

这时候可以把[]换成(),变成生成器表达式:它不会立即生成所有元素,而是在遍历的时候(比如for循环)才一个个“吐出来”,大幅节省内存:

# 生成器表达式(注意用小括号)
big_gen = (x**2 for x in range(1000000))

# 遍历时才计算
for num in big_gen:
    if num > 100:
        break  # 可以提前终止,不用算完所有

7. Python 3.8+小扩展:海象运算符

Python 3.8 引入的海象运算符:=,可以在列表生成式里先计算值、再判断是否保留,避免重复计算:

# 需求:计算1-10的平方,只保留大于50的
# 传统写法:x**2会算两次(一次在if,一次在结果)
result_old = [x**2 for x in range(1, 11) if x**2 > 50]

# 海象写法:x**2算一次,赋值给square,同时判断
result_new = [square for x in range(1, 11) if (square := x**2) > 50]
print(result_new)  # 输出: [64, 81, 100]

8. 小练习(附答案)

试试自己写一下:过滤列表L1 = ['Hello', 'World', 18, 'Apple', None],只保留字符串并转小写,最后打印结果。

点击查看答案
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s, str)]

# 简单测试
print(L2)
if L2 == ['hello', 'world', 'apple']:
    print('测试通过!🎉')
else:
    print('测试失败😅')

9. 总结:什么时候用列表生成式?

列表生成式是好东西,但别滥用!推荐以下场景用:

  1. 简单的「遍历→转换→过滤」逻辑(一行能写明白)
  2. 对代码简洁度有要求的小脚本

如果循环里有复杂的嵌套判断、多行转换逻辑,还是用传统for循环+注释吧——可读性永远是第一位的👍。