一、前言
在Python中,推导式和生成器是经过CPython底层极度优化的操作,不仅代码简洁优雅,执行速度也远超显式for循环。更重要的是,生成器采用惰性求值策略,处理海量数据时能大幅节省内存。
本文将系统介绍列表推导式、字典推导式、生成器表达式和生成器函数,并通过性能对比实验,帮助你掌握这些高性能编程技巧。
二、列表推导式:Pythonic的列表创建方式
2.1 基本语法
python
# 基础语法
[x for x in iterable]
[x for x in iterable if condition]
# 对比:传统循环 vs 推导式
# ❌ 5行代码
data = []
for i in range(10):
data.append(i)
# ✅ 1行代码
data = [x for x in range(10)]
# 带条件过滤
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
2.2 嵌套推导式
python
# 二维矩阵
matrix = [[i * j for j in range(3)] for i in range(3)]
# [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
# 展平二维列表
flat = [x for row in matrix for x in row]
# [0, 0, 0, 0, 1, 2, 0, 2, 4]
2.3 带else的推导式
python
# 偶数保留,奇数变0
nums = [1, 2, 3, 4, 5, 6]
result = [x if x % 2 == 0 else 0 for x in nums]
# [0, 2, 0, 4, 0, 6]
三、字典推导式与集合推导式
3.1 字典推导式
python
# 基础用法
squares = {i: i**2 for i in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 键值互换
data = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in data.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# 条件过滤
even_values = {k: v for k, v in data.items() if v % 2 == 0}
# {'b': 2}
3.2 集合推导式
python
# 去重并计算平方
nums = [1, 2, 2, 3, 3, 3]
unique_squares = {x**2 for x in nums} # {1, 4, 9}
四、生成器表达式:惰性求值的魅力
4.1 语法与特性
将列表推导式的[]改为(),即成为生成器表达式:
python
# 列表推导式 - 立即创建完整列表
list_comp = [x for x in range(10)]
print(type(list_comp)) # <class 'list'>
# 生成器表达式 - 惰性求值
gen_expr = (x for x in range(10))
print(type(gen_expr)) # <class 'generator'>
核心区别:
| 特性 | 列表推导式 | 生成器表达式 |
|---|---|---|
| 内存占用 | 存储所有元素 | 几乎不占内存 |
| 计算时机 | 立即计算 | 按需计算 |
| 可迭代次数 | 无限次 | 只能迭代一次 |
| 索引访问 | 支持 | 不支持 |
4.2 内存优势(关键!)
python
import sys
# 100万个整数
list_comp = [x for x in range(1000000)]
gen_expr = (x for x in range(1000000))
print(f"列表: {sys.getsizeof(list_comp) / 1024 / 1024:.2f} MB")
print(f"生成器: {sys.getsizeof(gen_expr) / 1024:.2f} KB")
典型输出 :列表占8MB,生成器仅0.11KB,差距超70000倍!
4.3 无限序列
python
import itertools
# 无限计数器
counter = itertools.count(start=0, step=1)
print(next(counter)) # 0
print(next(counter)) # 1
# 自定义无限斐波那契生成器
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=' ') # 0 1 1 2 3 5 8 13 21 34
五、生成器函数:yield的魔力
5.1 基本用法
使用yield代替return,函数变为生成器:
python
# 普通函数 - 一次性返回
def get_numbers(n):
return list(range(n))
# 生成器函数 - 逐个产生
def gen_numbers(n):
for i in range(n):
yield i
gen = gen_numbers(5)
print(list(gen)) # [0, 1, 2, 3, 4]
5.2 yield的执行特性
python
def simple_generator():
print("开始")
yield 1
print("继续")
yield 2
print("结束")
gen = simple_generator()
next(gen) # 输出: 开始
next(gen) # 输出: 继续
next(gen) # 输出: 结束,然后StopIteration
关键 :每次next()从上次yield处继续执行,实现"断点续传"。
5.3 yield from:委托子生成器
python
# 展平嵌套列表
def flatten(nested):
for sublist in nested:
yield from sublist
nested = [[1, 2, 3], [4, 5], [6]]
print(list(flatten(nested))) # [1, 2, 3, 4, 5, 6]
六、性能对比实战
6.1 速度对比
python
import time
n = 1000000
def loop():
res = []
for i in range(n):
res.append(i)
return sum(res)
def comprehension():
return sum([i for i in range(n)])
def generator():
return sum(i for i in range(n))
for name, func in [("loop", loop), ("comprehension", comprehension), ("generator", generator)]:
start = time.time()
result = func()
print(f"{name:15s}: {time.time() - start:.4f}秒")
典型结果:
loop : 0.0905秒
comprehension : 0.0826秒 ← 比循环快10%
generator : 0.0730秒 ← 比推导式快12%
6.2 内存对比
python
from memory_profiler import memory_usage
n = 100000
nums = range(n)
# 列表推导式 - 创建中间列表
def map_comprehension(nums):
a = [n * 2 for n in nums]
b = [n ** 2 for n in a]
c = [n ** 0.33 for n in b]
return max(c)
# map链式调用 - 惰性求值
def map_normal(nums):
a = map(lambda n: n * 2, nums)
b = map(lambda n: n ** 2, a)
c = map(lambda n: n ** 0.33, b)
return max(c)
print(f"列表推导式: {max(memory_usage((map_comprehension, (nums,)))):.2f} MiB")
print(f"map惰性求值: {max(memory_usage((map_normal, (nums,)))):.2f} MiB")
结果 :列表推导式多占用约10MB 内存,惰性求值几乎零额外内存!
七、实战场景
7.1 大文件处理
python
# ❌ 错误:一次性读入内存
with open('huge.log', 'r') as f:
lines = f.readlines() # 内存爆炸!
# ✅ 正确:逐行读取(文件对象本身就是迭代器)
with open('huge.log', 'r') as f:
error_count = sum(1 for line in f if 'ERROR' in line)
7.2 数据处理管道
python
def data_pipeline(raw_data):
# 过滤 → 转换 → 去重,全程惰性求值
valid = (row for row in raw_data if row is not None)
converted = ({k: int(v) for k, v in row.items()} for row in valid)
seen = set()
for row in converted:
key = row.get('id')
if key not in seen:
seen.add(key)
yield row
# 处理千万级数据,内存占用恒定
for item in data_pipeline(huge_dataset):
process(item)
7.3 配合Counter统计频率
python
from collections import Counter
# ❌ 错误:O(N²)
for item in set(data):
count = data.count(item)
# ✅ 正确:O(N)
counts = Counter(data)
八、总结与最佳实践
8.1 复杂度速查
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 中小数据量 | 列表/字典推导式 | 代码简洁,C优化 |
| 大数据量 | 生成器表达式 | 内存优化 |
| 复杂状态/无限序列 | 生成器函数 | 灵活控制 |
| 链式转换 | map()或生成器 |
惰性求值,零中间内存 |
8.2 黄金法则
- 能用推导式就不用循环:更快、更短、更Pythonic
- 大数据必用生成器:内存从GB降到KB
- 链式操作优先惰性求值:避免中间集合
- 统计频率用Counter :替代循环
count()
8.3 决策流程
需要创建序列?
├── 数据量小且需多次使用? → 推导式
├── 数据量大或内存敏感? → 生成器
└── 复杂逻辑或无限序列? → 生成器函数(yield)
推导式和生成器是Python"优雅"与"高效"完美结合的典范。掌握它们,你就从"能写Python"进阶到了"精通Python"。
如果本文对你有帮助,欢迎点赞、收藏、关注专栏!