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. 总结:什么时候用列表生成式?
列表生成式是好东西,但别滥用!推荐以下场景用:
- 简单的「遍历→转换→过滤」逻辑(一行能写明白)
- 对代码简洁度有要求的小脚本
如果循环里有复杂的嵌套判断、多行转换逻辑,还是用传统for循环+注释吧——可读性永远是第一位的👍。