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"。

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

相关推荐
青 春 记 忆3 小时前
Dify Docker Compose 通用无损升级指南:从备份、双版本预演到切换与回滚
运维·人工智能·python·docker·容器
GlueNa2SiO33 小时前
03-Flask模板引擎Jinja2详解
笔记·python·flask
临沂GEO6 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白6 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲7 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)9 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机9 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor6929 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
迷迭香yy10 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽11 小时前
真实即粗糙,光滑是伪造
人工智能·python