08.python中的yield

Python 中的 yield 详解

1. 什么是 yield

1.1 yield 的基本概念

yield 是 Python 中的一个关键字,用于定义生成器函数(Generator Function)。当一个函数中包含 yield 关键字时,这个函数就变成了一个生成器函数。

简单理解

  • yield 类似于 return,但不会终止函数的执行
  • 使用 yield 的函数可以”暂停”执行,并在需要时”恢复”执行
  • 每次 yield 会返回一个值,但函数的状态会被保存,下次调用时从上次暂停的地方继续

1.2 为什么需要 yield

在传统的函数中,使用 return 返回一个值后,函数就结束了。但有时候我们需要:

  • 按需生成数据:不需要一次性生成所有数据,而是需要时才生成
  • 节省内存:处理大量数据时,不需要将所有数据都加载到内存中
  • 实现协程:在异步编程中,可以暂停和恢复函数的执行

1.3 最简单的 yield 示例

让我们从一个最简单的例子开始:

# 示例1:最简单的生成器函数
def simple_generator():
    print("开始执行")
    yield 1
    print("继续执行")
    yield 2
    print("继续执行")
    yield 3
    print("执行结束")

# 调用生成器函数,返回一个生成器对象
gen = simple_generator()
print(type(gen))  # <class 'generator'>

# 使用 next() 函数获取下一个值
print(next(gen))  # 输出:开始执行,然后输出:1
print(next(gen))  # 输出:继续执行,然后输出:2
print(next(gen))  # 输出:继续执行,然后输出:3
# print(next(gen))  # 如果继续调用,会抛出 StopIteration 异常

运行结果

<class 'generator'>
开始执行
1
继续执行
2
继续执行
3

关键点

  • 调用生成器函数不会立即执行函数体,而是返回一个生成器对象
  • 每次调用 next() 时,函数会执行到下一个 yield 语句
  • yield 后面的值会被返回,函数会暂停在这里
  • 再次调用 next() 时,函数从上次暂停的地方继续执行

2. yield 与 return 的区别

2.1 基本区别对比

特性 return yield
函数类型 普通函数 生成器函数
返回值 立即返回一个值 返回一个生成器对象
执行状态 函数执行完毕,状态丢失 函数暂停,状态保存
多次调用 每次调用都从头开始 从上次暂停的地方继续
内存占用 需要存储所有返回值 只存储当前状态

2.2 对比示例:return vs yield

# 示例2:使用 return 的普通函数
def normal_function():
    return 1
    return 2  # 这行代码永远不会执行
    return 3  # 这行代码永远不会执行

result = normal_function()
print(result)  # 输出:1

# 示例3:使用 yield 的生成器函数
def generator_function():
    yield 1
    yield 2
    yield 3

gen = generator_function()
print(next(gen))  # 输出:1
print(next(gen))  # 输出:2
print(next(gen))  # 输出:3

关键区别

  • return 执行后函数立即结束,后面的代码不会执行
  • yield 执行后函数暂停,下次调用时继续执行后面的代码

2.3 生成多个值的对比

# 示例4:普通函数返回列表(需要一次性生成所有值)
def get_numbers_list(n):
    result = []
    for i in range(n):
        result.append(i * i)
    return result  # 返回完整的列表

numbers = get_numbers_list(5)
print(numbers)  # 输出:[0, 1, 4, 9, 16]
print(f"内存占用:需要存储 {len(numbers)} 个元素")

# 示例5:生成器函数(按需生成值)
def get_numbers_generator(n):
    for i in range(n):
        yield i * i  # 每次只生成一个值

gen = get_numbers_generator(5)
print(list(gen))  # 输出:[0, 1, 4, 9, 16]
# 注意:生成器只能遍历一次
# print(list(gen))  # 输出:[]

# 或者逐个获取
gen = get_numbers_generator(5)
for num in gen:
    print(num, end=" ")  # 输出:0 1 4 9 16

3. 生成器函数和生成器对象

3.1 生成器函数(Generator Function)

生成器函数是包含 yield 关键字的函数。定义生成器函数和定义普通函数的方法相同,只是使用 yield 代替 return

# 示例6:生成器函数的定义
def my_generator():
    yield "第一个值"
    yield "第二个值"
    yield "第三个值"

3.2 生成器对象(Generator Object)

调用生成器函数时,不会执行函数体,而是返回一个生成器对象。

# 示例7:生成器对象
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

# 调用生成器函数,返回生成器对象
gen = count_up_to(5)
print(gen)  # 输出:<generator object count_up_to at 0x...>
print(type(gen))  # 输出:<class 'generator'>

3.3 生成器对象的特性

生成器对象是一个迭代器(Iterator),具有以下特性:

  • 可以使用 next() 函数获取下一个值
  • 可以使用 for 循环遍历
  • 只能遍历一次
  • 是惰性求值的(Lazy Evaluation)
# 示例8:生成器对象的多种使用方式
def number_generator():
    yield 10
    yield 20
    yield 30

gen = number_generator()

# 方式1:使用 next() 函数
print(next(gen))  # 输出:10
print(next(gen))  # 输出:20
print(next(gen))  # 输出:30

# 方式2:使用 for 循环
gen = number_generator()
for num in gen:
    print(num)  # 输出:10, 20, 30

# 方式3:转换为列表
gen = number_generator()
print(list(gen))  # 输出:[10, 20, 30]

# 方式4:使用 * 解包
gen = number_generator()
print(*gen)  # 输出:10 20 30

4. yield 的基本用法

4.1 生成数字序列

# 示例9:生成从 1 到 n 的数字
def number_sequence(n):
    for i in range(1, n + 1):
        yield i

# 使用生成器
for num in number_sequence(5):
    print(num, end=" ")  # 输出:1 2 3 4 5

4.2 生成斐波那契数列

# 示例10:生成斐波那契数列
def fibonacci(n):
    """生成前 n 个斐波那契数"""
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

# 生成前 10 个斐波那契数
for num in fibonacci(10):
    print(num, end=" ")  # 输出:0 1 1 2 3 5 8 13 21 34

4.3 生成无限序列

# 示例11:生成无限的自然数序列
def natural_numbers():
    """生成无限的自然数序列"""
    num = 1
    while True:
        yield num
        num += 1

# 使用生成器(注意:不要直接转换为列表,会无限循环)
gen = natural_numbers()
print(next(gen))  # 输出:1
print(next(gen))  # 输出:2
print(next(gen))  # 输出:3

# 获取前 10 个自然数
gen = natural_numbers()
for i, num in enumerate(gen):
    if i >= 10:
        break
    print(num, end=" ")  # 输出:1 2 3 4 5 6 7 8 9 10

4.4 处理文件内容

# 示例12:逐行读取文件(内存友好)
def read_file_lines(filename):
    """逐行读取文件,每次只加载一行到内存"""
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()  # 去除行尾的换行符

# 使用生成器处理大文件
for line in read_file_lines('large_file.txt'):
    print(line)
    # 处理每一行,不需要一次性加载整个文件到内存

4.5 过滤数据

# 示例13:过滤偶数
def filter_even(numbers):
    """过滤出偶数"""
    for num in numbers:
        if num % 2 == 0:
            yield num

# 使用生成器过滤
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = filter_even(numbers)
print(list(even_numbers))  # 输出:[2, 4, 6, 8, 10]

4.6 转换数据

# 示例14:将数字转换为平方
def square_numbers(numbers):
    """将数字列表转换为平方"""
    for num in numbers:
        yield num ** 2

# 使用生成器转换
numbers = [1, 2, 3, 4, 5]
squares = square_numbers(numbers)
print(list(squares))  # 输出:[1, 4, 9, 16, 25]

5. 生成器的执行流程

5.1 执行流程详解

理解生成器的执行流程对于掌握 yield 非常重要。让我们通过一个详细的例子来说明:

# 示例15:详细演示生成器的执行流程
def detailed_generator():
    print("步骤1:函数开始执行")
    yield "第一个值"
    print("步骤2:从第一个 yield 后继续执行")
    yield "第二个值"
    print("步骤3:从第二个 yield 后继续执行")
    yield "第三个值"
    print("步骤4:函数执行完毕")

# 创建生成器对象(此时函数体还没有执行)
print("创建生成器对象...")
gen = detailed_generator()
print("生成器对象已创建,但函数体尚未执行n")

# 第一次调用 next()
print("第一次调用 next():")
result1 = next(gen)
print(f"返回值:{result1}n")

# 第二次调用 next()
print("第二次调用 next():")
result2 = next(gen)
print(f"返回值:{result2}n")

# 第三次调用 next()
print("第三次调用 next():")
result3 = next(gen)
print(f"返回值:{result3}n")

# 第四次调用 next() 会抛出 StopIteration 异常
print("第四次调用 next()(会抛出异常):")
try:
    result4 = next(gen)
except StopIteration as e:
    print(f"捕获到 StopIteration 异常:{e}")

运行结果

创建生成器对象...
生成器对象已创建,但函数体尚未执行

第一次调用 next():
步骤1:函数开始执行
返回值:第一个值

第二次调用 next():
步骤2:从第一个 yield 后继续执行
返回值:第二个值

第三次调用 next():
步骤3:从第二个 yield 后继续执行
返回值:第三个值

第四次调用 next()(会抛出异常):
步骤4:函数执行完毕
捕获到 StopIteration 异常:

5.2 执行流程图解

调用生成器函数
    ↓
返回生成器对象(函数体未执行)
    ↓
第一次调用 next()
    ↓
执行到第一个 yield → 返回第一个值 → 暂停
    ↓
第二次调用 next()
    ↓
从第一个 yield 后继续执行 → 执行到第二个 yield → 返回第二个值 → 暂停
    ↓
第三次调用 next()
    ↓
从第二个 yield 后继续执行 → 执行到第三个 yield → 返回第三个值 → 暂停
    ↓
第四次调用 next()
    ↓
从第三个 yield 后继续执行 → 函数执行完毕 → 抛出 StopIteration 异常

5.3 带循环的生成器执行流程

# 示例16:带循环的生成器执行流程
def loop_generator(n):
    print(f"生成器开始,n = {n}")
    for i in range(n):
        print(f"循环中,i = {i}")
        yield i * 2
        print(f"yield 后继续,i = {i}")
    print("生成器结束")

gen = loop_generator(3)

print("第一次调用 next():")
print(f"返回值:{next(gen)}n")

print("第二次调用 next():")
print(f"返回值:{next(gen)}n")

print("第三次调用 next():")
print(f"返回值:{next(gen)}n")

运行结果

第一次调用 next():
生成器开始,n = 3
循环中,i = 0
返回值:0

第二次调用 next():
yield 后继续,i = 0
循环中,i = 1
返回值:2

第三次调用 next():
yield 后继续,i = 1
循环中,i = 2
返回值:4

6. 生成器的常用方法

6.1 next() 方法

next() 函数用于获取生成器的下一个值。

# 示例17:使用 next() 方法
def simple_gen():
    yield 1
    yield 2
    yield 3

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

6.2 send() 方法

send() 方法可以向生成器发送值,这个值会成为 yield 表达式的返回值。

# 示例18:使用 send() 方法向生成器发送值
def generator_with_send():
    print("开始执行")
    value1 = yield "第一个值"
    print(f"收到 send 的值:{value1}")
    value2 = yield "第二个值"
    print(f"收到 send 的值:{value2}")
    yield "第三个值"

gen = generator_with_send()

# 第一次必须使用 next() 或 send(None) 启动生成器
print(next(gen))  # 输出:开始执行,然后输出:第一个值

# 使用 send() 发送值
print(gen.send("Hello"))  # 输出:收到 send 的值:Hello,然后输出:第二个值

# 继续发送值
print(gen.send("World"))  # 输出:收到 send 的值:World,然后输出:第三个值

重要提示

  • 第一次调用生成器时,必须使用 next()send(None) 来启动生成器
  • send() 的值会成为 yield 表达式的返回值
  • 如果生成器还没有执行到第一个 yield,不能使用 send() 发送非 None 的值

6.3 throw() 方法

throw() 方法用于在生成器中抛出异常。

# 示例19:使用 throw() 方法抛出异常
def generator_with_throw():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError as e:
        print(f"捕获到异常:{e}")
        yield "异常处理后的值"

gen = generator_with_throw()
print(next(gen))  # 输出:1

# 在生成器中抛出异常
print(gen.throw(ValueError, "这是一个错误"))  # 输出:捕获到异常:这是一个错误,然后输出:异常处理后的值

6.4 close() 方法

close() 方法用于关闭生成器,会在生成器中抛出 GeneratorExit 异常。

# 示例20:使用 close() 方法关闭生成器
def generator_with_close():
    try:
        yield 1
        yield 2
        yield 3
    except GeneratorExit:
        print("生成器被关闭")

gen = generator_with_close()
print(next(gen))  # 输出:1

# 关闭生成器
gen.close()
print("生成器已关闭")

6.5 综合示例:所有方法的使用

# 示例21:综合使用生成器的各种方法
def advanced_generator():
    print("生成器启动")
    try:
        value = yield "初始值"
        print(f"收到值:{value}")

        while True:
            try:
                value = yield f"处理值:{value}"
                print(f"收到值:{value}")
            except ValueError as e:
                print(f"捕获 ValueError:{e}")
                value = yield f"错误处理:{e}"
    except GeneratorExit:
        print("生成器正常关闭")
    finally:
        print("清理资源")

gen = advanced_generator()

# 启动生成器
print(next(gen))  # 输出:生成器启动,然后输出:初始值

# 发送值
print(gen.send("Hello"))  # 输出:收到值:Hello,然后输出:处理值:Hello

# 抛出异常
print(gen.throw(ValueError, "测试错误"))  # 输出:捕获 ValueError:测试错误,然后输出:错误处理:测试错误

# 关闭生成器
gen.close()  # 输出:生成器正常关闭,然后输出:清理资源

7. yield 的进阶用法

7.1 yield from(委托生成器)

yield from 是 Python 3.3 引入的语法,用于委托给另一个生成器。

# 示例22:使用 yield from 委托生成器
def inner_generator():
    yield 1
    yield 2
    yield 3

def outer_generator():
    yield "开始"
    yield from inner_generator()  # 委托给 inner_generator
    yield "结束"

# 使用 yield from
for value in outer_generator():
    print(value, end=" ")  # 输出:开始 1 2 3 结束

7.2 yield from 的优势

yield from 可以简化代码,避免手动迭代:

# 示例23:不使用 yield from(繁琐的方式)
def generator_without_yield_from():
    yield 1
    for value in range(2, 5):
        yield value
    yield 5

# 示例24:使用 yield from(简洁的方式)
def generator_with_yield_from():
    yield 1
    yield from range(2, 5)
    yield 5

# 两种方式结果相同
print(list(generator_without_yield_from()))  # 输出:[1, 2, 3, 4, 5]
print(list(generator_with_yield_from()))     # 输出:[1, 2, 3, 4, 5]

7.3 yield from 处理嵌套生成器

# 示例25:处理嵌套的生成器
def level1():
    yield "Level 1 - 1"
    yield "Level 1 - 2"

def level2():
    yield "Level 2 - 1"
    yield from level1()
    yield "Level 2 - 2"

def level3():
    yield "Level 3 - 1"
    yield from level2()
    yield "Level 3 - 2"

# 使用嵌套生成器
for value in level3():
    print(value)

输出

Level 3 - 1
Level 2 - 1
Level 1 - 1
Level 1 - 2
Level 2 - 2
Level 3 - 2

7.4 带返回值的 yield from

yield from 还可以接收子生成器的返回值:

# 示例26:yield from 接收返回值
def sub_generator():
    yield 1
    yield 2
    return "子生成器完成"  # 返回值会被 yield from 接收

def main_generator():
    result = yield from sub_generator()
    yield f"主生成器收到:{result}"

gen = main_generator()
for value in gen:
    print(value)

输出

1
2
主生成器收到:子生成器完成

7.5 协程(Coroutine)基础

生成器可以用作协程,实现简单的协作式多任务:

# 示例27:简单的协程示例
def producer():
    """生产者协程"""
    for i in range(5):
        value = yield f"产品 {i}"
        print(f"生产者收到反馈:{value}")

def consumer(prod):
    """消费者协程"""
    next(prod)  # 启动生产者
    for i in range(5):
        product = prod.send(f"消费反馈 {i}")
        print(f"消费者收到:{product}")

# 使用协程
prod = producer()
consumer(prod)

7.6 双向通信的生成器

# 示例28:双向通信的生成器
def echo_generator():
    """回显生成器,将收到的值原样返回"""
    while True:
        value = yield
        yield value  # 返回收到的值

gen = echo_generator()
next(gen)  # 启动生成器

print(gen.send("Hello"))  # 输出:Hello
next(gen)  # 跳过 yield None
print(gen.send("World"))  # 输出:World

8. 生成器表达式

8.1 什么是生成器表达式

生成器表达式(Generator Expression)是创建生成器的简洁语法,类似于列表推导式,但使用圆括号而不是方括号。

# 示例29:生成器表达式的基本语法
# 列表推导式(立即生成所有值)
squares_list = [x**2 for x in range(5)]
print(squares_list)  # 输出:[0, 1, 4, 9, 16]

# 生成器表达式(按需生成值)
squares_gen = (x**2 for x in range(5))
print(squares_gen)  # 输出:<generator object <genexpr> at 0x...>
print(list(squares_gen))  # 输出:[0, 1, 4, 9, 16]

8.2 生成器表达式 vs 列表推导式

# 示例30:内存使用对比
import sys

# 列表推导式:占用更多内存
numbers_list = [x**2 for x in range(1000000)]
print(f"列表大小:{sys.getsizeof(numbers_list)} 字节")

# 生成器表达式:占用很少内存
numbers_gen = (x**2 for x in range(1000000))
print(f"生成器大小:{sys.getsizeof(numbers_gen)} 字节")

8.3 生成器表达式的应用

# 示例31:使用生成器表达式处理数据
# 计算前 10 个偶数的平方和
sum_of_squares = sum(x**2 for x in range(2, 21, 2))
print(sum_of_squares)  # 输出:1540

# 过滤数据
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares = (x**2 for x in numbers if x % 2 == 0)
print(list(even_squares))  # 输出:[4, 16, 36, 64, 100]

8.4 嵌套生成器表达式

# 示例32:嵌套生成器表达式
# 生成所有可能的坐标对
coordinates = ((x, y) for x in range(3) for y in range(3))
print(list(coordinates))  
# 输出:[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

9. 实际应用场景

9.1 读取大文件

处理大文件时,使用生成器可以避免内存溢出:

# 示例33:逐行读取大文件
def read_large_file(filename):
    """逐行读取大文件,避免内存溢出"""
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# 处理大文件
for line in read_large_file('large_file.txt'):
    # 处理每一行
    if 'error' in line.lower():
        print(f"发现错误行:{line}")

9.2 数据管道处理

生成器可以构建数据管道,实现流式处理:

# 示例34:数据管道处理
def read_numbers():
    """读取数字"""
    for i in range(1, 11):
        yield i

def filter_even(numbers):
    """过滤偶数"""
    for num in numbers:
        if num % 2 == 0:
            yield num

def square(numbers):
    """计算平方"""
    for num in numbers:
        yield num ** 2

# 构建数据管道
pipeline = square(filter_even(read_numbers()))
print(list(pipeline))  # 输出:[4, 16, 36, 64, 100]

9.3 无限序列生成

生成器非常适合生成无限序列:

# 示例35:生成无限序列
def count_from(start):
    """从指定数字开始无限计数"""
    while True:
        yield start
        start += 1

def fibonacci_infinite():
    """无限斐波那契数列"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 获取前 10 个斐波那契数
fib = fibonacci_infinite()
for i, num in enumerate(fib):
    if i >= 10:
        break
    print(num, end=" ")  # 输出:0 1 1 2 3 5 8 13 21 34

9.4 分页处理数据

# 示例36:分页处理数据
def paginate(data, page_size=10):
    """分页生成器"""
    for i in range(0, len(data), page_size):
        yield data[i:i + page_size]

# 使用分页生成器
data = list(range(100))
for page in paginate(data, page_size=20):
    print(f"处理页面:{page[:5]}...")  # 只显示前5个元素

9.5 实现迭代器协议

生成器自动实现了迭代器协议:

# 示例37:自定义可迭代对象
class NumberRange:
    """自定义数字范围类"""
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __iter__(self):
        """返回生成器作为迭代器"""
        current = self.start
        while current < self.end:
            yield current
            current += 1

# 使用自定义可迭代对象
num_range = NumberRange(1, 6)
for num in num_range:
    print(num, end=" ")  # 输出:1 2 3 4 5

9.6 状态机实现

生成器可以用于实现简单的状态机:

# 示例38:使用生成器实现状态机
def state_machine():
    """简单的状态机"""
    state = "开始"
    while True:
        if state == "开始":
            command = yield "状态:开始"
            if command == "下一步":
                state = "处理中"
        elif state == "处理中":
            command = yield "状态:处理中"
            if command == "完成":
                state = "结束"
            elif command == "返回":
                state = "开始"
        elif state == "结束":
            yield "状态:结束"
            break

# 使用状态机
sm = state_machine()
print(next(sm))  # 输出:状态:开始
print(sm.send("下一步"))  # 输出:状态:处理中
print(sm.send("完成"))  # 输出:状态:结束

9.7 数据流处理

# 示例39:数据流处理示例
def data_source():
    """模拟数据源"""
    for i in range(1, 11):
        yield {"id": i, "value": i * 10}

def filter_data(data_stream, min_value=30):
    """过滤数据"""
    for item in data_stream:
        if item["value"] >= min_value:
            yield item

def transform_data(data_stream):
    """转换数据"""
    for item in data_stream:
        item["value"] = item["value"] * 2
        yield item

# 构建数据处理管道
pipeline = transform_data(filter_data(data_source(), min_value=30))
for item in pipeline:
    print(item)

输出

{'id': 3, 'value': 60}
{'id': 4, 'value': 80}
{'id': 5, 'value': 100}
{'id': 6, 'value': 120}
{'id': 7, 'value': 140}
{'id': 8, 'value': 160}
{'id': 9, 'value': 180}
{'id': 10, 'value': 200}

10. 常见问题与注意事项

10.1 生成器只能遍历一次

生成器是一次性的,遍历完后就不能再次使用:

# 示例40:生成器只能遍历一次
def simple_gen():
    yield 1
    yield 2
    yield 3

gen = simple_gen()
print(list(gen))  # 输出:[1, 2, 3]
print(list(gen))  # 输出:[](已经遍历完了)

解决方案:如果需要多次遍历,可以重新创建生成器:

# 示例41:重新创建生成器
def get_numbers():
    yield 1
    yield 2
    yield 3

# 第一次遍历
gen1 = get_numbers()
print(list(gen1))  # 输出:[1, 2, 3]

# 第二次遍历(重新创建)
gen2 = get_numbers()
print(list(gen2))  # 输出:[1, 2, 3]

10.2 yield 不能在普通函数中使用

yield 只能在生成器函数中使用,不能在普通函数中使用:

# 示例42:错误用法
def normal_function():
    return 1
    yield 2  # 这行代码永远不会执行,但不会报错

# 实际上,包含 yield 的函数就是生成器函数
gen = normal_function()
print(type(gen))  # 输出:<class 'generator'>

10.3 生成器函数中的 return

在生成器函数中,return 用于结束生成器,但不能返回值(Python 3.3+ 可以返回值,但会被 yield from 接收):

# 示例43:生成器函数中的 return
def generator_with_return():
    yield 1
    yield 2
    return "结束"  # 这个返回值不能直接获取
    # yield 3  # 这行代码永远不会执行

gen = generator_with_return()
for value in gen:
    print(value)  # 输出:1, 2

10.4 异常处理

生成器中的异常处理需要特别注意:

# 示例44:生成器中的异常处理
def generator_with_exception():
    try:
        yield 1
        yield 2
        raise ValueError("测试异常")
        yield 3
    except ValueError as e:
        print(f"捕获异常:{e}")
        yield "异常处理"

gen = generator_with_exception()
for value in gen:
    print(value)

输出

1
2
捕获异常:测试异常
异常处理

10.5 内存泄漏问题

虽然生成器可以节省内存,但如果不正确使用,也可能导致问题:

# 示例45:避免在生成器中保存大量引用
def problematic_generator():
    large_data = [x for x in range(1000000)]  # 这会占用大量内存
    for item in large_data:
        yield item

# 更好的方式
def better_generator():
    for x in range(1000000):  # 不保存所有数据
        yield x

10.6 生成器的性能考虑

# 示例46:性能对比
import time

# 方式1:使用列表
def process_with_list(n):
    numbers = [x**2 for x in range(n)]
    return sum(numbers)

# 方式2:使用生成器
def process_with_generator(n):
    numbers = (x**2 for x in range(n))
    return sum(numbers)

# 对于小数据量,性能差异不大
# 对于大数据量,生成器可以节省内存

11. 性能优势

11.1 内存使用对比

生成器的主要优势是节省内存:

# 示例47:内存使用对比
import sys

# 列表:占用大量内存
numbers_list = [x for x in range(1000000)]
print(f"列表内存占用:{sys.getsizeof(numbers_list)} 字节")

# 生成器:占用很少内存
numbers_gen = (x for x in range(1000000))
print(f"生成器内存占用:{sys.getsizeof(numbers_gen)} 字节")

11.2 延迟计算(惰性求值)

生成器采用延迟计算,只在需要时才计算值:

# 示例48:延迟计算示例
def expensive_operation(n):
    """模拟耗时操作"""
    print(f"计算 {n} 的平方...")
    return n ** 2

def lazy_generator():
    """延迟计算的生成器"""
    for i in range(5):
        yield expensive_operation(i)

# 只有在实际获取值时才执行计算
gen = lazy_generator()
print("生成器已创建,但还没有执行计算")

# 现在才开始计算
print(next(gen))  # 输出:计算 0 的平方...,然后输出:0
print(next(gen))  # 输出:计算 1 的平方...,然后输出:1

11.3 处理无限序列

生成器可以处理无限序列,而列表不能:

# 示例49:处理无限序列
def infinite_sequence():
    """无限序列生成器"""
    num = 0
    while True:
        yield num
        num += 1

# 可以安全地创建无限序列生成器
gen = infinite_sequence()

# 获取前 10 个值
for i, value in enumerate(gen):
    if i >= 10:
        break
    print(value, end=" ")  # 输出:0 1 2 3 4 5 6 7 8 9

11.4 流式处理大数据

生成器非常适合流式处理大数据:

# 示例50:流式处理大数据
def process_large_dataset():
    """处理大型数据集"""
    # 模拟从数据库或文件读取大量数据
    for i in range(1000000):
        # 每次只处理一条记录,不占用大量内存
        yield {"id": i, "data": f"记录 {i}"}

# 流式处理,内存占用很小
for record in process_large_dataset():
    # 处理每条记录
    if record["id"] % 100000 == 0:
        print(f"处理进度:{record['id']}")

12. 总结

12.1 核心要点回顾

  1. yield 关键字

    • yield 用于定义生成器函数
    • 函数中包含 yield 就自动成为生成器函数
    • yield 会暂停函数执行,保存状态,下次从暂停处继续
  2. 生成器对象

    • 调用生成器函数返回生成器对象
    • 生成器对象是迭代器,可以使用 next()for 循环
    • 生成器只能遍历一次
  3. 主要优势

    • 节省内存:按需生成值,不需要一次性生成所有数据
    • 延迟计算:只在需要时才计算值
    • 处理无限序列:可以生成和处理无限序列
    • 代码简洁:使用生成器表达式可以写出更简洁的代码
  4. 常用方法

    • next():获取下一个值
    • send():向生成器发送值
    • throw():在生成器中抛出异常
    • close():关闭生成器
  5. 进阶用法

    • yield from:委托给另一个生成器
    • 生成器表达式:创建生成器的简洁语法
    • 协程:使用生成器实现协作式多任务

12.2 使用建议

  1. 何时使用生成器

    • 处理大量数据时
    • 需要按需生成数据时
    • 实现数据管道时
    • 处理无限序列时
  2. 何时不使用生成器

    • 需要多次遍历数据时(除非重新创建生成器)
    • 需要随机访问数据时(生成器不支持索引)
    • 数据量很小,性能差异可以忽略时
  3. 最佳实践

    • 使用 yield from 简化嵌套生成器
    • 使用生成器表达式处理简单场景
    • 注意生成器只能遍历一次的特性
    • 合理使用异常处理

12.3 学习路径建议

对于新手,建议按以下顺序学习:

  1. 第一步:理解 yield 的基本概念和与 return 的区别
  2. 第二步:掌握生成器函数和生成器对象的基本用法
  3. 第三步:学习生成器的执行流程和常用方法
  4. 第四步:练习使用生成器解决实际问题
  5. 第五步:学习 yield from 和生成器表达式
  6. 第六步:探索生成器在协程和异步编程中的应用

12.4 进一步学习

掌握了 yield 的基础后,可以进一步学习:

  • 异步编程async/await 语法(Python 3.5+)
  • 协程:使用生成器实现更复杂的协程
  • 迭代器协议:深入理解 Python 的迭代器机制
  • 函数式编程:结合 mapfilterreduce 等函数使用生成器

附录:完整示例代码

附录A:综合示例 – 数据管道

# 完整的数据处理管道示例
def read_data():
    """读取数据"""
    for i in range(1, 21):
        yield {"id": i, "value": i * 10}

def filter_data(data_stream, threshold=50):
    """过滤数据"""
    for item in data_stream:
        if item["value"] >= threshold:
            yield item

def transform_data(data_stream):
    """转换数据"""
    for item in data_stream:
        item["value"] = item["value"] * 2
        item["processed"] = True
        yield item

def aggregate_data(data_stream):
    """聚合数据"""
    total = 0
    count = 0
    for item in data_stream:
        total += item["value"]
        count += 1
        yield item
    yield {"summary": {"total": total, "count": count}}

# 构建完整的数据处理管道
pipeline = aggregate_data(
    transform_data(
        filter_data(
            read_data(),
            threshold=50
        )
    )
)

# 处理数据
for item in pipeline:
    print(item)

附录B:综合示例 – 文件处理

# 完整的文件处理示例
def read_file_lines(filename):
    """逐行读取文件"""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                yield {
                    "line_num": line_num,
                    "content": line.strip(),
                    "length": len(line.strip())
                }
    except FileNotFoundError:
        yield {"error": f"文件 {filename} 未找到"}

def filter_lines(line_stream, min_length=10):
    """过滤行"""
    for line_info in line_stream:
        if "error" in line_info:
            yield line_info
        elif line_info.get("length", 0) >= min_length:
            yield line_info

def process_lines(line_stream):
    """处理行"""
    for line_info in line_stream:
        if "error" not in line_info:
            line_info["processed"] = True
            line_info["upper"] = line_info["content"].upper()
        yield line_info

# 使用文件处理管道
pipeline = process_lines(
    filter_lines(
        read_file_lines("example.txt"),
        min_length=10
    )
)

for result in pipeline:
    print(result)

恭喜! 你已经完成了 Python 中 yield 的详细学习。记住,实践是掌握编程的关键,多写代码,多尝试不同的场景,你会越来越熟练地使用 yield 和生成器!

发表评论