Python高级特性:生成器完全指南

Python高级特性:生成器完全指南

按需生成,内存友好

前言

生成器(Generator)是Python中一种特殊的迭代器 ,它允许你按需生成值,而不是一次性生成所有值并存储在内存中。这在处理大数据集无限序列时特别有用。

本文将系统讲解生成器的概念、两种创建方式、高级特性、实际应用以及性能考量,帮助你掌握这一Python核心特性。


一、什么是生成器?

1.1 生成器的核心特点

特点 说明
惰性求值 按需生成值,不预先计算
节省内存 不需要存储所有元素
保持状态 可以暂停和恢复执行
一次性使用 只能迭代一次
语法简洁 类似函数,使用yield

1.2 生成器 vs 普通列表

python 复制代码
# 普通列表:一次性生成所有元素,占用大量内存
squares_list = [x**2 for x in range(1000000)]
print(f"列表内存: {squares_list.__sizeof__()} bytes")  # ~8MB

# 生成器:按需生成元素,几乎不占内存
squares_gen = (x**2 for x in range(1000000))
print(f"生成器内存: {squares_gen.__sizeof__()} bytes")  # ~104 bytes

# 生成器只在实际迭代时才计算值
for square in squares_gen:
    if square > 100:
        break
    print(square, end=' ')  # 0 1 4 9 16 25 36 49 64 81 100

📌 核心优势 :对于大数据集,生成器可以节省数百万倍的内存!


二、创建生成器的两种方法

2.1 生成器表达式

生成器表达式与列表推导式类似,但使用**圆括号()**而不是方括号[]

python 复制代码
# 列表推导式 - 立即生成所有元素
list_comp = [x**2 for x in range(10)]
print(f"列表推导式类型: {type(list_comp)}")  # <class 'list'>

# 生成器表达式 - 按需生成元素
gen_exp = (x**2 for x in range(10))
print(f"生成器表达式类型: {type(gen_exp)}")  # <class 'generator'>

# 使用生成器
for num in gen_exp:
    print(num, end=' ')  # 0 1 4 9 16 25 36 49 64 81

适用场景:简单的转换和过滤操作。

2.2 生成器函数

使用yield关键字定义的函数就是生成器函数:

python 复制代码
def count_up_to(max):
    """计数生成器,生成1到max的数字"""
    count = 1
    while count <= max:
        yield count  # 暂停并返回值
        count += 1

# 创建生成器对象
counter = count_up_to(5)
print(type(counter))  # <class 'generator'>

# 使用for循环迭代
for num in count_up_to(5):
    print(num, end=' ')  # 1 2 3 4 5

关键特性

  • 生成器函数在每次调用next()时执行到下一个yield语句
  • 然后暂停,保持所有局部变量的状态
  • 下次调用时从暂停处继续执行

三、生成器的使用

3.1 使用for循环迭代(推荐)

python 复制代码
def count_down(n):
    while n > 0:
        yield n
        n -= 1

# 使用for循环自动处理StopIteration
for num in count_down(5):
    print(num, end=' ')  # 5 4 3 2 1

3.2 手动控制(使用next())

python 复制代码
gen = count_up_to(3)

print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2
print(next(gen))  # 输出: 3
print(next(gen))  # 抛出StopIteration异常

# 安全的手动迭代
gen = count_up_to(3)
while True:
    try:
        value = next(gen)
        print(value)
    except StopIteration:
        break

四、生成器的高级特性

4.1 生成器状态

生成器保持其执行状态,包括局部变量和指令指针:

python 复制代码
def stateful_gen():
    print("开始执行")
    yield 1
    print("继续执行")
    yield 2
    print("结束执行")

gen = stateful_gen()
print(next(gen))  # 输出"开始执行",返回1
print(next(gen))  # 输出"继续执行",返回2
print(next(gen))  # 输出"结束执行",抛出StopIteration

输出:

sql 复制代码
开始执行
1
继续执行
2
结束执行
Traceback (most recent call last):
  ...
StopIteration

4.2 生成器返回值(Python 3.3+)

生成器可以使用return返回值,该值会在StopIteration异常中携带:

python 复制代码
def gen_with_return():
    yield 1
    yield 2
    return "完成"

gen = gen_with_return()
try:
    while True:
        print(next(gen))
except StopIteration as e:
    print(f"返回值: {e.value}")  # 输出: 返回值: 完成

4.3 生成器委托:yield from(Python 3.3+)

使用yield from可以委托给另一个生成器或可迭代对象:

python 复制代码
def chain_generators(*iterables):
    """将多个可迭代对象串联起来"""
    for it in iterables:
        yield from it  # 委托给子迭代器

# 示例:串联列表、元组、字符串
gen = chain_generators([1, 2], (3, 4), "ab")
result = list(gen)
print(result)  # [1, 2, 3, 4, 'a', 'b']

# 等价于:
def manual_chain(*iterables):
    for it in iterables:
        for item in it:
            yield item

4.4 生成器与协程:.send()方法

生成器也可以用于协程编程,通过.send()方法可以向生成器发送值:

python 复制代码
def coroutine():
    print("启动协程")
    while True:
        value = yield  # 接收发送的值
        print(f"接收到值: {value}")

co = coroutine()
next(co)  # 启动协程(必须先调用next或send(None))
co.send(10)  # 输出: "接收到值: 10"
co.send(20)  # 输出: "接收到值: 20"

# 带返回值的协程
def calculator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value
    return total

calc = calculator()
next(calc)  # 启动
print(calc.send(10))  # 输出: 10
print(calc.send(20))  # 输出: 30
print(calc.send(30))  # 输出: 60

五、实际应用示例

5.1 斐波那契数列生成器

python 复制代码
def fibonacci():
    """无限斐波那契数列生成器"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 获取前10个斐波那契数
fib = fibonacci()
fib_10 = [next(fib) for _ in range(10)]
print(fib_10)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# 获取小于100的所有斐波那契数
fib = fibonacci()
fib_under_100 = []
for num in fib:
    if num >= 100:
        break
    fib_under_100.append(num)
print(fib_under_100)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

5.2 读取大文件(内存友好)

python 复制代码
def read_large_file(file_path):
    """逐行读取大文件,不占用过多内存"""
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield line.strip()  # 去除换行符

# 逐行处理大文件(如10GB的日志文件)
def process_line(line):
    """处理每一行(示例函数)"""
    if "ERROR" in line:
        print(f"发现错误: {line[:50]}...")

# 使用生成器处理,内存占用恒定
for line in read_large_file('huge_log_file.txt'):
    process_line(line)

5.3 无限序列生成器

python 复制代码
def natural_numbers(start=1):
    """生成自然数序列(无限)"""
    n = start
    while True:
        yield n
        n += 1

def even_numbers():
    """生成偶数序列(无限)"""
    n = 0
    while True:
        yield n
        n += 2

def prime_numbers():
    """生成素数序列(无限)"""
    def is_prime(n):
        if n < 2:
            return False
        for i in range(2, int(n**0.5) + 1):
            if n % i == 0:
                return False
        return True
    
    n = 2
    while True:
        if is_prime(n):
            yield n
        n += 1

# 使用无限序列
primes = prime_numbers()
first_10_primes = [next(primes) for _ in range(10)]
print(first_10_primes)  # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

5.4 数据处理管道

python 复制代码
def read_lines(file_path):
    """读取文件行"""
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

def filter_lines(lines, keyword):
    """过滤包含关键字的行"""
    for line in lines:
        if keyword in line:
            yield line

def convert_to_uppercase(lines):
    """转换为大写"""
    for line in lines:
        yield line.upper()

def write_lines(lines, output_path):
    """写入文件"""
    with open(output_path, 'w') as f:
        for line in lines:
            f.write(line + '\n')

# 构建处理管道
input_file = 'data.txt'
output_file = 'output.txt'
keyword = 'ERROR'

# 管道:读取 -> 过滤 -> 转换 -> 写入
lines = read_lines(input_file)
filtered = filter_lines(lines, keyword)
uppercased = convert_to_uppercase(filtered)
write_lines(uppercased, output_file)

print(f"处理完成,结果保存到 {output_file}")

六、练习题:杨辉三角生成器

python 复制代码
def triangles():
    """杨辉三角生成器,逐行生成"""
    row = [1]
    while True:
        yield row
        # 计算下一行
        row = [1] + [row[i] + row[i+1] for i in range(len(row)-1)] + [1]

# 测试:生成前10行杨辉三角
results = []
n = 0
for t in triangles():
    results.append(t)
    n += 1
    if n == 10:
        break

# 打印杨辉三角
for i, row in enumerate(results, 1):
    # 格式化输出,使三角形居中显示
    print(f"第{i:2d}行: {row}")

# 输出示例:
# 第 1行: [1]
# 第 2行: [1, 1]
# 第 3行: [1, 2, 1]
# 第 4行: [1, 3, 3, 1]
# 第 5行: [1, 4, 6, 4, 1]
# ...

七、性能考虑

7.1 内存效率对比

数据结构 内存占用 适用场景
列表 高(所有元素) 小数据集、需要随机访问
生成器 极低(恒定) 大数据集、流式处理
元组 中等 不可变数据

7.2 速度对比

python 复制代码
import timeit

# 列表推导式
def test_list():
    return [x**2 for x in range(10000)]

# 生成器表达式
def test_generator():
    return (x**2 for x in range(10000))

# 求和性能测试
list_time = timeit.timeit('sum([x**2 for x in range(10000)])', number=1000)
gen_time = timeit.timeit('sum((x**2 for x in range(10000)))', number=1000)

print(f"列表求和: {list_time:.4f}秒")
print(f"生成器求和: {gen_time:.4f}秒")
# 生成器通常略慢(约10-20%),但内存占用小得多

7.3 一次性使用特性

python 复制代码
# 生成器只能迭代一次
gen = (x**2 for x in range(5))
print(list(gen))  # [0, 1, 4, 9, 16]
print(list(gen))  # [] - 已经耗尽!

# 需要多次使用可以转换为列表或重新创建
values = list((x**2 for x in range(5)))  # 转换为列表
print(values)  # [0, 1, 4, 9, 16]
print(values)  # [0, 1, 4, 9, 16] - 可以多次使用

八、总结

知识点 要点
定义 按需生成值的迭代器
创建方式1 生成器表达式:(expr for item in iterable)
创建方式2 生成器函数:使用yield关键字
核心优势 节省内存、支持无限序列
限制 一次性使用、速度略慢
高级特性 yield from委托、.send()协程、返回值
适用场景 大数据集、流式处理、无限序列、管道处理

最佳实践

  1. ✅ 处理大文件时使用生成器逐行读取
  2. ✅ 需要无限序列时使用生成器
  3. ✅ 构建数据处理管道时使用生成器
  4. ✅ 需要节省内存时优先考虑生成器
  5. ⚠️ 需要随机访问或多次迭代时使用列表
  6. ⚠️ 对速度要求极高且数据量小时使用列表

生成器是Python中优雅而强大的工具,掌握它可以让你的代码更高效、更Pythonic!


💡 Python 学习不走弯路! 体系化实战路线:基础语法 · 异步Web开发 · 数据采集 · 计算机视觉 · NLP · 大模型RAG实战 ------ 全在「道满PythonAI」! 点赞 + 关注,持续更新干货!

📚 相关推荐阅读


如果这篇文章对你有帮助,欢迎点赞、评论、收藏,你的支持是我持续分享的动力!🎉

相关推荐
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒2 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境2 小时前
提升 Python 代码健壮性的方法大盘点
后端·python