Python-12 Python生成器与yield:惰性求值的艺术

目录

专栏导读

🌸 欢迎来到Python办公自动化专栏---Python处理办公问题,解放您的双手
🏳️‍🌈 个人博客主页:请点击------> 个人的博客主页 求收藏
🏳️‍🌈 Github主页:请点击------> Github主页 求Star⭐
🏳️‍🌈 知乎主页:请点击------> 知乎主页 求关注
🏳️‍🌈 CSDN博客主页:请点击------> CSDN的博客主页 求关注
👍 该系列文章专栏:请点击------>Python办公自动化专栏 求订阅
🕷 此外还有爬虫专栏:请点击------>Python爬虫基础专栏 求订阅
📕 此外还有python基础专栏:请点击------>Python基础学习专栏 求订阅
文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
❤️ 欢迎各位佬关注! ❤️

前言

引言

生成器(Generator)是Python中一个强大而优雅的特性,它让我们能够创建惰性求值的迭代器。与一次性生成所有数据的列表不同,生成器按需生成数据,大大节省了内存。本文将深入探讨生成器的工作原理、使用场景和高级技巧。

生成器基础

什么是生成器

生成器是一种特殊的迭代器,它使用yield关键字来产生值。与普通函数不同,生成器函数在每次调用yield时会暂停执行,保存当前状态,并在下次迭代时从暂停处继续执行。

python 复制代码
def simple_generator():
    yield 1
    yield 2
    yield 3

# 使用生成器
gen = simple_generator()
print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2
print(next(gen))  # 输出: 3
# print(next(gen))  # StopIteration

生成器 vs 列表

python 复制代码
import sys
import time

def list_version():
    """返回包含100万个数字的列表"""
    return [i * 2 for i in range(1000000)]

def generator_version():
    """生成100万个数字的生成器"""
    for i in range(1000000):
        yield i * 2

# 内存对比
list_data = list_version()
print(f"列表内存占用: {sys.getsizeof(list_data)} 字节")

gen_data = generator_version()
print(f"生成器内存占用: {sys.getsizeof(gen_data)} 字节")

# 性能对比
def time_list():
    start = time.time()
    result = [i * 2 for i in range(1000000)]
    return time.time() - start

def time_generator():
    start = time.time()
    result = (i * 2 for i in range(1000000))  # 生成器表达式
    # 强制求值以对比性能
    list(result)
    return time.time() - start

print(f"列表创建时间: {time_list():.4f}秒")
print(f"生成器创建时间: {time_generator():.4f}秒")

yield详解

yield的工作原理

python 复制代码
def fibonacci_generator(n):
    """生成斐波那契数列"""
    a, b = 0, 1
    count = 0
    while count < n:
        print(f"生成第{count + 1}个值: {a}")
        yield a
        a, b = b, a + b
        count += 1
        print(f"继续执行,当前状态: a={a}, b={b}")

# 逐步执行观察yield的行为
fib_gen = fibonacci_generator(5)
print("第一次调用:")
value1 = next(fib_gen)
print(f"获得值: {value1}")

print("\n第二次调用:")
value2 = next(fib_gen)
print(f"获得值: {value2}")

yield与return的区别

python 复制代码
def with_return():
    """普通函数使用return"""
    print("开始执行")
    return 1
    print("这行代码不会执行")
    return 2

def with_yield():
    """生成器函数使用yield"""
    print("开始执行")
    yield 1
    print("继续执行")
    yield 2
    print("执行结束")

# 对比执行
print("=== return版本 ===")
result = with_return()
print(f"结果: {result}")

print("\n=== yield版本 ===")
gen = with_yield()
print("第一次调用:")
val1 = next(gen)
print(f"获得值: {val1}")

print("\n第二次调用:")
val2 = next(gen)
print(f"获得值: {val2}")

print("\n第三次调用:")
try:
    val3 = next(gen)
except StopIteration:
    print("生成器结束")

生成器表达式

基本语法

生成器表达式是创建生成器的简洁方式,语法类似于列表推导式,但使用圆括号:

python 复制代码
# 列表推导式(立即求值)
list_comp = [x * 2 for x in range(5)]
print(f"列表推导式结果: {list_comp}")

# 生成器表达式(惰性求值)
gen_expr = (x * 2 for x in range(5))
print(f"生成器表达式结果: {gen_expr}")
print(f"转换为列表: {list(gen_expr)}")

性能对比

python 复制代码
import memory_profiler
import time

def memory_test_list():
    """测试列表推导式的内存使用"""
    # 创建包含1000万个数字的列表
    data = [i ** 2 for i in range(10000000)]
    return sum(data)

def memory_test_generator():
    """测试生成器表达式的内存使用"""
    # 创建生成1000万个数字的生成器
    data = (i ** 2 for i in range(10000000))
    return sum(data)

# 内存使用对比(简化版本)
print("=== 内存使用对比 ===")

# 列表版本
start_mem = memory_profiler.memory_usage()[0]
start_time = time.time()
result_list = memory_test_list()
list_time = time.time() - start_time
list_mem = memory_profiler.memory_usage()[0] - start_mem

print(f"列表版本 - 结果: {result_list}")
print(f"列表版本 - 时间: {list_time:.2f}秒")

# 生成器版本
start_mem = memory_profiler.memory_usage()[0]
start_time = time.time()
result_gen = memory_test_generator()
gen_time = time.time() - start_time
gen_mem = memory_profiler.memory_usage()[0] - start_mem

print(f"生成器版本 - 结果: {result_gen}")
print(f"生成器版本 - 时间: {gen_time:.2f}秒")

高级生成器技巧

生成器委托(yield from)

python 复制代码
def subgenerator():
    """子生成器"""
    yield 1
    yield 2
    return "子生成器完成"

def main_generator():
    """主生成器,委托给子生成器"""
    yield 0
    result = yield from subgenerator()  # 委托给子生成器
    print(f"子生成器返回值: {result}")
    yield 3

# 使用生成器委托
gen = main_generator()
for value in gen:
    print(f"主生成器产出: {value}")

双向通信

生成器不仅可以产出值,还可以接收值:

python 复制代码
def echo_generator():
    """回显生成器,接收并返回发送的值"""
    value = yield "准备就绪"  # 第一次yield产出初始值
    while True:
        received = yield f"收到: {value}"
        value = received

# 双向通信示例
echo = echo_generator()
print(next(echo))  # 启动生成器

print(echo.send("Hello"))
print(echo.send("World"))
print(echo.send("Python"))

throw和close方法

python 复制代码
def robust_generator():
    """演示异常处理的生成器"""
    try:
        yield 1
        yield 2
        yield 3
    except ValueError as e:
        print(f"捕获到ValueError: {e}")
        yield f"错误处理: {e}"
    finally:
        print("生成器清理工作")
        yield "清理完成"

# 测试异常处理
gen = robust_generator()
print(next(gen))  # 产出1
print(next(gen))  # 产出2

# 向生成器抛出异常
try:
    result = gen.throw(ValueError, "测试异常")
    print(f"异常处理结果: {result}")
except StopIteration:
    print("生成器结束")

# 关闭生成器
gen2 = robust_generator()
next(gen2)
gen2.close()  # 触发GeneratorExit

实际应用案例

1. 大文件处理

python 复制代码
def read_large_file(file_path, chunk_size=1024):
    """分块读取大文件"""
    with open(file_path, 'rb') as file:
        while True:
            chunk = file.read(chunk_size)
            if not chunk:
                break
            yield chunk

def process_large_file(file_path):
    """处理大文件,统计行数"""
    def read_lines():
        with open(file_path, 'r', encoding='utf-8') as file:
            for line in file:
                yield line.strip()
    
    line_count = 0
    word_count = 0
    
    for line in read_lines():
        line_count += 1
        word_count += len(line.split())
        
        # 每1000行输出一次进度
        if line_count % 1000 == 0:
            print(f"已处理{line_count}行,{word_count}个单词")
    
    return line_count, word_count

# 创建测试文件
test_content = ""
for i in range(10000):
    test_content += f"这是第{i}行测试数据,包含一些单词\n"

with open('test_large_file.txt', 'w', encoding='utf-8') as f:
    f.write(test_content)

# 测试大文件处理
lines, words = process_large_file('test_large_file.txt')
print(f"总行数: {lines}, 总单词数: {words}")

2. 无限序列生成

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

def prime_generator():
    """生成素数序列"""
    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

# 使用无限序列
fib_gen = infinite_fibonacci()
fib_10 = [next(fib_gen) for _ in range(10)]
print(f"前10个斐波那契数: {fib_10}")

prime_gen = prime_generator()
prime_10 = [next(prime_gen) for _ in range(10)]
print(f"前10个素数: {prime_10}")

3. 数据管道处理

python 复制代码
def data_cleaner(data_source):
    """数据清洗生成器"""
    for item in data_source:
        # 去除空白字符
        cleaned = item.strip()
        # 过滤空字符串
        if cleaned:
            yield cleaned

def data_transformer(cleaned_data):
    """数据转换生成器"""
    for item in cleaned_data:
        # 转换为大写
        yield item.upper()

def data_filter(transformed_data, min_length=3):
    """数据过滤生成器"""
    for item in transformed_data:
        # 过滤长度小于最小值的字符串
        if len(item) >= min_length:
            yield item

# 创建数据处理管道
def data_pipeline(raw_data):
    """完整的数据处理管道"""
    cleaned = data_cleaner(raw_data)
    transformed = data_transformer(cleaned)
    filtered = data_filter(transformed)
    return filtered

# 测试数据管道
raw_data = ["  hello  ", "", "  world  ", "  py  ", "  python  ", "  ai  "]
processed_data = list(data_pipeline(raw_data))
print(f"处理结果: {processed_data}")

4. 异步生成器模拟

python 复制代码
import asyncio
import random

async def async_data_generator():
    """异步数据生成器模拟"""
    for i in range(5):
        # 模拟异步操作
        await asyncio.sleep(random.uniform(0.1, 0.5))
        yield f"异步数据{i}"

# 同步版本(用于对比)
def sync_data_generator():
    """同步数据生成器"""
    for i in range(5):
        # 模拟耗时操作
        time.sleep(random.uniform(0.1, 0.5))
        yield f"同步数据{i}"

# 测试同步生成器
print("=== 同步生成器测试 ===")
start_time = time.time()
for data in sync_data_generator():
    print(f"收到数据: {data}")
sync_time = time.time() - start_time
print(f"同步总耗时: {sync_time:.2f}秒")

生成器模式与设计

生成器工厂模式

python 复制代码
def generator_factory(start, step=1):
    """生成器工厂,创建不同类型的序列生成器"""
    def arithmetic_sequence():
        """等差数列生成器"""
        current = start
        while True:
            yield current
            current += step
    
    def geometric_sequence():
        """等比数列生成器"""
        current = start
        while True:
            yield current
            current *= step
    
    def fibonacci_like():
        """类斐波那契数列生成器"""
        a, b = start, step
        while True:
            yield a
            a, b = b, a + b
    
    return {
        'arithmetic': arithmetic_sequence,
        'geometric': geometric_sequence,
        'fibonacci': fibonacci_like
    }

# 使用生成器工厂
factory = generator_factory(1, 2)

# 创建不同类型的生成器
arith_gen = factory['arithmetic']()
geo_gen = factory['geometric']()
fib_gen = factory['fibonacci']()

print("等差数列前5项:", [next(arith_gen) for _ in range(5)])
print("等比数列前5项:", [next(geo_gen) for _ in range(5)])
print("类斐波那契前5项:", [next(fib_gen) for _ in range(5)])

状态机生成器

python 复制代码
def state_machine_generator():
    """使用生成器实现状态机"""
    state = 'START'
    
    while True:
        if state == 'START':
            input_val = yield '开始状态'
            if input_val == 'go':
                state = 'PROCESSING'
            else:
                state = 'ERROR'
                
        elif state == 'PROCESSING':
            input_val = yield '处理中'
            if input_val == 'complete':
                state = 'COMPLETE'
            elif input_val == 'error':
                state = 'ERROR'
            else:
                yield '继续处理中'
                
        elif state == 'COMPLETE':
            yield '完成状态'
            state = 'START'  # 重置到开始状态
            
        elif state == 'ERROR':
            yield '错误状态'
            state = 'START'  # 重置到开始状态

# 测试状态机
sm = state_machine_generator()
print(next(sm))  # 开始状态
print(sm.send('go'))  # 处理中
print(sm.send('processing'))  # 继续处理中
print(sm.send('complete'))  # 完成状态
print(next(sm))  # 开始状态(重置)

性能优化与最佳实践

内存使用优化

python 复制代码
def memory_efficient_processing():
    """内存高效的数据处理示例"""
    
    # 传统方法:一次性加载所有数据
    def traditional_method(filename):
        with open(filename, 'r') as f:
            lines = f.readlines()  # 所有数据加载到内存
        
        results = []
        for line in lines:
            processed = line.strip().upper()
            if len(processed) > 5:
                results.append(processed)
        
        return results
    
    # 生成器方法:流式处理
    def generator_method(filename):
        with open(filename, 'r') as f:
            for line in f:  # 逐行处理
                processed = line.strip().upper()
                if len(processed) > 5:
                    yield processed
    
    # 创建测试文件
    test_file = 'memory_test.txt'
    with open(test_file, 'w') as f:
        for i in range(100000):
            f.write(f"这是第{i}行测试数据\n")
    
    # 测试内存使用
    import tracemalloc
    
    # 测试传统方法
    tracemalloc.start()
    traditional_result = traditional_method(test_file)
    traditional_memory = tracemalloc.get_traced_memory()[1]
    tracemalloc.stop()
    
    # 测试生成器方法
    tracemalloc.start()
    generator_result = list(generator_method(test_file))
    generator_memory = tracemalloc.get_traced_memory()[1]
    tracemalloc.stop()
    
    print(f"传统方法内存使用: {traditional_memory / 1024 / 1024:.2f} MB")
    print(f"生成器方法内存使用: {generator_memory / 1024 / 1024:.2f} MB")
    print(f"结果数量: {len(traditional_result)} = {len(generator_result)}")

# 运行内存测试
memory_efficient_processing()

生成器链式调用

python 复制代码
def chained_generators():
    """生成器链式调用示例"""
    
    def source_generator():
        """数据源生成器"""
        for i in range(20):
            yield i
    
    def filter_even(source):
        """过滤偶数"""
        for item in source:
            if item % 2 == 0:
                yield item
    
    def multiply_by_ten(source):
        """乘以10"""
        for item in source:
            yield item * 10
    
    def take_first_n(source, n):
        """取前n个"""
        count = 0
        for item in source:
            if count >= n:
                break
            yield item
            count += 1
    
    # 链式调用
    result = take_first_n(
        multiply_by_ten(
            filter_even(
                source_generator()
            )
        ),
        5
    )
    
    return list(result)

# 测试链式生成器
print("链式生成器结果:", chained_generators())

常见陷阱与调试

生成器只能迭代一次

python 复制代码
def single_use_generator():
    """生成器只能使用一次的演示"""
    yield 1
    yield 2
    yield 3

# 错误使用方式
gen = single_use_generator()
print("第一次迭代:", list(gen))
print("第二次迭代:", list(gen))  # 空列表,生成器已耗尽

# 正确使用方式
def reusable_generator_func():
    """返回新的生成器实例"""
    def generator():
        yield 1
        yield 2
        yield 3
    return generator()

# 每次调用都获得新的生成器
print("第一次使用:", list(reusable_generator_func()))
print("第二次使用:", list(reusable_generator_func()))

生成器与异常处理

python 复制代码
def exception_handling_generator():
    """生成器中的异常处理"""
    try:
        yield 1
        yield 2
        # 这里可能会抛出异常
        yield 3 / 0  # 故意制造异常
        yield 4
    except ZeroDivisionError as e:
        print(f"生成器内部捕获异常: {e}")
        yield f"错误处理结果: {e}"
    finally:
        print("生成器清理完成")

# 测试异常处理
gen = exception_handling_generator()
try:
    print(next(gen))  # 1
    print(next(gen))  # 2
    print(next(gen))  # 触发异常处理
    print(next(gen))  # 错误处理结果
except StopIteration:
    print("生成器自然结束")

总结

生成器是Python中处理大数据和流式数据的强大工具。通过惰性求值,生成器能够:

  1. 节省内存:只在需要时生成数据
  2. 提高性能:避免不必要的计算
  3. 简化代码:用同步的方式编写异步逻辑
  4. 支持无限序列:处理理论上的无限数据流

掌握生成器的使用,能够让你的Python代码更加高效、优雅。在实际应用中,要根据具体场景选择合适的迭代方式,平衡内存使用和性能需求。

练习题

  1. 创建一个生成器,生成前n个三角数
  2. 使用生成器实现一个无限的随机游走序列
  3. 创建一个生成器管道,处理日志文件并提取错误信息
  4. 实现一个生成器版本的map函数
  5. 使用生成器实现一个内存高效的斐波那契数列生成器

记住:生成器不仅仅是一种语法特性,更是一种编程思维方式的转变------从"拥有数据"到"生成数据"。这种思维方式在处理现代大数据应用时尤为重要。

结尾

希望对初学者有帮助;致力于办公自动化的小小程序员一枚
希望能得到大家的【❤️一个免费关注❤️】感谢!
求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍
此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏
此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏
此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏
相关推荐
小陈phd20 分钟前
RAG从入门到精通(十四)——评估技术
人工智能·python
卡次卡次121 分钟前
注意点:多线程与多进程与在并行读-->并行cpu或者GPU处理--->并行写 的架构中,如何选择
linux·网络·python
wearegogog12323 分钟前
使用MATLAB实现平方倍频法对DSSS/BPSK信号进行载频估计
开发语言·matlab
简单的话*26 分钟前
Logback 日志按月归档并保留 180 天,超期自动清理的配置实践
java·前端·python
蓝眸少年CY28 分钟前
Python科学计算 Numpy库
开发语言·python·numpy
困惑阿三29 分钟前
深入理解 JavaScript 中的(Promise.race)
开发语言·前端·javascript·ecmascript·reactjs
我命由我1234531 分钟前
微信小程序 bind:tap 与 bindtap 的区别
开发语言·前端·javascript·微信小程序·小程序·前端框架·js
hmbbcsm39 分钟前
列表,字典,集合三者之间的相互转换以及各自的推导式
python
Yolo566Q41 分钟前
基于ArcGIS、InVEST与RUSLE水土流失模拟及分析
开发语言·python