f += [x.replace('./', parent, 1) if x.startswith('./') else x for x in t]
一、什么是列表推导式?
列表推导式 (List Comprehension)是 Python 提供的一种声明式 (declarative)语法,用于从一个或多个可迭代对象(如列表、元组、字符串、生成器等)中简洁地构建新列表 。其核心思想是:"你想要什么"(what),而不是**"如何一步步得到它"**(how)。
它不是函数,也不是关键字,而是一种语法糖(syntactic sugar)。
二、基本语法结构
最通用的列表推导式语法如下:
[<表达式> for <变量> in <可迭代对象> [if <条件>]]
更完整的扩展形式(支持嵌套和多条件)为:
[<表达式>
for <var1> in <iter1>
[for <var2> in <iter2>]
...
[if <condition1>]
[if <condition2>]
...
]
关键组成部分说明:
| 部分 | 作用 | 是否必需 |
|---|---|---|
<表达式> |
对每个元素进行变换或计算的结果 | ✅ 必需 |
for <变量> in <可迭代对象> |
遍历数据源 | ✅ 至少一个 |
if <条件> |
过滤元素(仅保留满足条件的) | ❌ 可选 |
嵌套 for |
多重循环(如笛卡尔积) | ❌ 可选 |
三、基础示例与等价传统写法
1. 最简单形式:无条件、无过滤
squares = [x**2 for x in range(5)]
# → [0, 1, 4, 9, 16]
等价于:
squares = []
for x in range(5):
squares.append(x**2)
2. 带过滤条件(if 在末尾)
evens = [x for x in range(10) if x % 2 == 0]
# → [0, 2, 4, 6, 8]
等价于:
evens = []
for x in range(10):
if x % 2 == 0:
evens.append(x)
注意:这里的
if是过滤器,只保留满足条件的元素。
3. 带条件表达式(三元运算符,在表达式位置)
signs = ['+' if x >= 0 else '-' for x in [-2, -1, 0, 1, 2]]
# → ['-', '-', '+', '+', '+']
等价于:
signs = []
for x in [-2, -1, 0, 1, 2]:
if x >= 0:
signs.append('+')
else:
signs.append('-')
重要区别:
if在末尾 → 过滤元素if-else在开头 (表达式中)→ 对每个元素做不同处理
四、嵌套列表推导式(多重循环)
1. 两个 for 循环(笛卡尔积)
pairs = [(x, y) for x in [1, 2] for y in ['a', 'b']]
# → [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
等价于:
pairs = []
for x in [1, 2]:
for y in ['a', 'b']:
pairs.append((x, y))
执行顺序:外层在左,内层在右 。即先固定
x=1,遍历所有y;再x=2,遍历所有y。
2. 生成二维列表(矩阵)
matrix = [[i * j for j in range(3)] for i in range(3)]
# → [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
等价于:
matrix = []
for i in range(3):
row = []
for j in range(3):
row.append(i * j)
matrix.append(row)
3. 展平嵌套列表(flatten)
nested = [[1, 2], [3, 4], [5]]
flat = [item for sublist in nested for item in sublist]
# → [1, 2, 3, 4, 5]
注意顺序:
for sublist in nested先,for item in sublist后。
五、结合过滤与嵌套
# 所有 x < y 的 (x, y) 对,其中 x,y ∈ [0,1,2]
result = [(x, y) for x in range(3) for y in range(3) if x < y]
# → [(0, 1), (0, 2), (1, 2)]
多个 if 条件等价于 and:
[x for x in range(10) if x % 2 == 0 if x > 5]
# 等价于 if x % 2 == 0 and x > 5
# → [6, 8]
六、作用域与变量泄漏(Python 2 vs Python 3)
Python 2 中的问题(已修复):
在 Python 2 中,列表推导式的循环变量会泄漏到外部作用域:
# Python 2
x = 'outer'
dummy = [x for x in [1, 2]]
print(x) # 输出 2!变量被覆盖
Python 3 的改进:
从 Python 3 开始,列表推导式拥有自己的局部作用域,不会污染外部变量:
# Python 3
x = 'outer'
dummy = [x for x in [1, 2]]
print(x) # 仍输出 'outer'
这是 Python 3 的一个重要改进,提升了安全性。
七、性能分析:为什么列表推导式更快?
列表推导式通常比等价的 for + append() 循环快 10%~30%,原因如下:
- 减少函数调用开销 :
list.append是方法调用,有额外开销;而列表推导式在 C 层直接构建列表。 - 字节码优化 :CPython 对列表推导式有专门的字节码指令(如
LIST_APPEND)。 - 内存预分配:某些情况下可预估大小,减少动态扩容。
性能测试示例(使用 timeit):
import timeit
# 方法1:列表推导式
t1 = timeit.timeit('[x**2 for x in range(1000)]', number=10000)
# 方法2:传统循环
t2 = timeit.timeit('''
lst = []
for x in range(1000):
lst.append(x**2)
''', number=10000)
print(f"列表推导式: {t1:.4f}s")
print(f"传统循环: {t2:.4f}s")
# 通常 t1 < t2
但注意:可读性优先于微优化。只有在性能关键路径才需考虑。
八、与其他函数式工具对比
1. vs map() 和 lambda
# 列表推导式
squares = [x**2 for x in range(5)]
# map + lambda
squares = list(map(lambda x: x**2, range(5)))
推荐列表推导式:更清晰,无需 lambda,且直接返回 list(map 返回迭代器)。
2. vs filter()
# 列表推导式
evens = [x for x in range(10) if x % 2 == 0]
# filter
evens = list(filter(lambda x: x % 2 == 0, range(10)))
列表推导式更直观,避免 lambda。
3. 组合使用(不推荐)
# 不推荐:嵌套 map/filter
result = list(map(lambda x: x**2, filter(lambda x: x > 0, range(-2, 3))))
# 推荐:列表推导式
result = [x**2 for x in range(-2, 3) if x > 0]
Guido van Rossum(Python 之父)曾表示:列表推导式是
map/filter的更好替代。
九、高级用法与技巧
1. 使用生成器表达式(节省内存)
如果你不需要立即生成整个列表,可用生成器表达式(圆括号):
gen = (x**2 for x in range(1000000)) # 不占用大量内存
total = sum(gen) # 惰性求值
列表推导式:
[...]→ 立即创建完整列表生成器表达式:
(...)→ 惰性迭代,节省内存
2. 与 enumerate() 结合
indexed = [(i, char.upper()) for i, char in enumerate("hello")]
# → [(0, 'H'), (1, 'E'), (2, 'L'), (3, 'L'), (4, 'O')]
3. 与字典/集合推导式类比
Python 还支持:
-
集合推导式 :
{x for x in ...} -
字典推导式 :
{k: v for k, v in ...}字典:将列表转为索引映射
words = ['apple', 'banana']
index_map = {word: i for i, word in enumerate(words)}→ {'apple': 0, 'banana': 1}
4. 异常处理?不能直接写!
列表推导式不支持 try-except。若需异常处理,应封装函数:
def safe_int(s):
try:
return int(s)
except ValueError:
return None
data = ['1', '2', 'abc', '3']
nums = [safe_int(x) for x in data if safe_int(x) is not None]
# → [1, 2, 3]
或使用
filter+ 函数,但通常建议提前清洗数据。
十、常见陷阱与注意事项
1. 可变对象陷阱(浅拷贝问题)
# 错误:所有子列表共享同一个引用
matrix = [[]] * 3 # 不要用!
matrix[0].append(1)
print(matrix) # [[1], [1], [1]] ❌
# 正确:用列表推导式创建独立子列表
matrix = [[] for _ in range(3)]
matrix[0].append(1)
print(matrix) # [[1], [], []] ✅
2. 过度嵌套降低可读性
# 难以阅读
result = [func(a, b) for a in list1 for b in list2 if cond1(a) and cond2(b)]
# 建议拆分为函数或普通循环
def process():
for a in list1:
if not cond1(a): continue
for b in list2:
if cond2(b):
yield func(a, b)
result = list(process())
PEP 8 建议:单行不超过 79/88 字符。复杂逻辑应拆分。
3. 不要用于副作用(如打印、写文件)
# 反模式:用列表推导式做副作用
[print(x) for x in range(3)] # 会创建 [None, None, None] 浪费内存
# 正确做法
for x in range(3):
print(x)
列表推导式应用于构建数据,而非执行操作。
十一、实际应用场景举例
场景1:数据清洗
raw = [" Alice ", "Bob\n", "", " Charlie "]
clean = [name.strip().title() for name in raw if name.strip()]
# → ['Alice', 'Bob', 'Charlie']
场景2:路径处理(如你最初的问题)
parent = "/home/user"
files = ["./a.txt", "b.log", "./sub/c.py"]
resolved = [
x.replace('./', parent + '/', 1) if x.startswith('./') else x
for x in files
]
# → ['/home/user/a.txt', 'b.log', '/home/user/sub/c.py']
场景3:提取 JSON 中特定字段
users = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]
names = [u['name'] for u in users if u['age'] > 25]
# → ['Alice']
十二、总结:何时使用列表推导式?
✅ 推荐使用当:
- 逻辑简单(1~2 层循环)
- 目的是转换+过滤数据
- 可读性高、代码简洁
❌ 避免使用当:
- 需要异常处理
- 有复杂副作用(如 I/O)
- 嵌套超过两层
- 逻辑难以一眼看懂
十三、附录:语法速查表
| 功能 | 列表推导式写法 |
|---|---|
| 基本映射 | [f(x) for x in items] |
| 过滤 | [x for x in items if cond(x)] |
| 映射+过滤 | [f(x) for x in items if cond(x)] |
| 条件分支 | [f(x) if cond(x) else g(x) for x in items] |
| 笛卡尔积 | [f(x, y) for x in A for y in B] |
| 展平列表 | [item for sub in nested for item in sub] |
| 带索引 | [(i, x) for i, x in enumerate(items)] |
十四、常见应用场景
-
数据清洗:过滤无效值、格式转换
1clean = [s.strip().lower() for s in strings if s.strip()] -
路径处理(如你之前的问题):
1paths = [p.replace('./', base, 1) if p.startswith('./') else p for p in raw_paths] -
矩阵转置:
1matrix = [[1, 2], [3, 4], [5, 6]] 2transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
十五、总结口诀:
"前表达,中循环,后过滤;若要分支,三元放前面。"