Python推导式与生成器

一、前言

在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 黄金法则

  1. 能用推导式就不用循环:更快、更短、更Pythonic
  2. 大数据必用生成器:内存从GB降到KB
  3. 链式操作优先惰性求值:避免中间集合
  4. 统计频率用Counter :替代循环count()

8.3 决策流程

复制代码
需要创建序列?
├── 数据量小且需多次使用? → 推导式
├── 数据量大或内存敏感? → 生成器
└── 复杂逻辑或无限序列? → 生成器函数(yield)

推导式和生成器是Python"优雅"与"高效"完美结合的典范。掌握它们,你就从"能写Python"进阶到了"精通Python"。

如果本文对你有帮助,欢迎点赞、收藏、关注专栏!

相关推荐
高洁016 小时前
AI技术分享:如何做好职场内部技术培训
python·深度学习·知识图谱
E_ICEBLUE6 小时前
使用 Python 在 PowerPoint 中添加或移除背景(图像与颜色)
python·powerpoint
研究点啥好呢6 小时前
华为数据分析工程师面试题精选:10道高频考题+答案解析
python·sql·面试·求职招聘
财经资讯数据_灵砚智能7 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年4月29日
人工智能·python·信息可视化·自然语言处理·ai编程
AI玫瑰助手7 小时前
Python基础:输入input与输出print函数详解
开发语言·windows·python
郝学胜-神的一滴7 小时前
干货版《算法导论》 02 :算法效率核心解密
java·开发语言·数据结构·c++·python·算法
WL_Aurora7 小时前
Python 算法基础篇之回溯
python·算法
码农的日常搅屎棍7 小时前
segmentation-models-pytorch 极简实战:快速搭建与训练高精度语义分割模型
人工智能·pytorch·python