python-生成器(Generator)详解

昨天发布了python迭代器相关内容,在python 中还有一种特殊的迭代器,叫生成器,通过延迟计算(Lazy Evaluation)的方式,按需生成序列中的每个元素,而不是一次性将所有元素加载到内存中。生成器的核心是 yield 关键字,它允许函数在返回值后暂停执行,稍后可以从暂停的位置继续。

1、 设计思路

生成器的设计思路主要围绕以下几个关键点:

  • 惰性计算:生成器按需生成数据,避免浪费内存。
  • 代码简洁:生成器使用 yield 关键字自动保存函数的状态,不需要手动实现复杂的迭代器协议。
  • 无限序列支持:生成器可以用于表示无限序列,避免固定大小的限制。
  • 可读性和扩展性:相比传统迭代器,生成器的实现更简洁,代码可维护性更高。

2生成器的用途和用法

生成器在以下场景中特别有用:

处理大规模数据:如读取大文件、处理海量数据时,生成器可以逐行或逐块读取,避免内存不足。

实现惰性序列:如无限序列生成(斐波那契数列、自然数序列等)。

简化迭代器的实现:相比手动实现迭代器协议,生成器的实现更为简便。

生成器有两种创建方式:

  1. 使用生成器函数(包含 yield 的普通函数)。
  2. 使用生成器表达式(与列表推导式类似,但使用圆括号)。

2.1 使用生成器函数

生成器函数是普通的 python 函数,但它包含 yield 关键字。每次调用生成器的 __next__() 方法时,函数会从上次暂停的地方恢复执行。

示例:生成一个有限的数列

def count_up_to(limit):

    current = 1

    while current <= limit:

        yield current  # 暂停并返回当前值

        current += 1

# 使用生成器
gen = count_up_to(5)

for num in gen:

    print(num)

输出:

1

2

3

4

5

2.2 使用生成器表达式

生成器表达式是一种简洁的生成器定义方式,类似于列表推导式,但它使用圆括号而不是方括号。

示例:生成平方数

# 生成器表达式
squares = (x**2 for x in range(1, 6))

# 使用生成器
for square in squares:

    print(square)

输出:

1

4

9

16

25

3生成器的特点

状态自动保存:每次调用 yield 时,生成器会自动保存当前的执行状态(包括局部变量和程序位置)。下一次调用时,从暂停的地方继续执行。

示例:状态保存

def generator_example():

    print("First yield")

    yield 1

    print("Second yield")

    yield 2


gen = generator_example()

print(next(gen))  # 输出 "First yield" 和 1
print(next(gen))  # 输出 "Second yield" 和 2

输出:

First yield

1

Second yield

2

惰性求值(Lazy Evaluation):生成器只在需要时生成值,从而节省内存,特别适用于处理大数据或无限序列。

示例:无限序列

def infinite_numbers():

    current = 1

    while True:

        yield current

        current += 1

gen = infinite_numbers()

for _ in range(5):

    print(next(gen))  # 按需生成前 5 个数字

输出:

1

2

3

4

5

单次遍历
生成器只能遍历一次,因为一旦迭代完成,生成器的状态就会被丢弃。如果需要多次遍历,必须重新创建生成器。

示例:单次遍历

def simple_generator():

    yield "A"

    yield "B"

gen = simple_generator()

print(list(gen))  # ['A', 'B']
print(list(gen))  # 空列表,因为生成器已经耗尽

输出:

['A', 'B']

[]

异常处理
在生成器中可以使用 try-except 块处理异常,也可以通过外部调用 throw() 方法向生成器注入异常。

示例:生成器中的异常处理

def generator_with_exception():

    try:

        yield "Start"
        yield "Middle"

    except ValueError:

        yield "Exception handled"

    yield "End"

gen = generator_with_exception()

print(next(gen))  # 输出 "Start"
print(gen.throw(ValueError))  # 向生成器注入异常,输出 "Exception handled"
print(next(gen))  # 输出 "End"

输出:

Start

Exception handled

End

支持 send() 方法:生成器可以通过 send() 方法接收外部传入的值并继续执行。

示例:生成器接收外部值

def accumulator():

    total = 0

    while True:

        value = yield total

        if value is None:

            break

        total += value

gen = accumulator()

print(next(gen))  # 初始化生成器,输出 0
print(gen.send(10))  # 输入 10,输出累加结果 10
print(gen.send(20))  # 输入 20,输出累加结果 30

gen.close()  # 关闭生成器

输出:

0

10

30

4、  生成器的注意事项

只能前进:生成器是一次性迭代的工具,无法回退或重新开始。如果需要多次访问相同的数据,考虑使用列表或重新创建生成器。

StopIteration 异常:当生成器函数结束时,会自动抛出 StopIteration 异常。这通常由 for 循环或内置函数(如 list())自动处理,用户无需手动捕获。

资源管理:如果生成器涉及外部资源(如文件操作),确保在不需要时关闭生成器(使用 close() 方法或上下文管理器)。

示例:关闭生成器

def file_reader(file_path):

    with open(file_path, 'r') as file:

        for line in file:

            yield line.strip()

gen = file_reader("example.txt")

try:

    print(next(gen))

finally:

    gen.close()

5、  生成器的高级用法

嵌套生成器 (yield from)
yield from 用于委托另一个生成器。

示例:嵌套生成器

def sub_generator():

    yield 1

    yield 2

def main_generator():

    yield from sub_generator()

    yield 3

for value in main_generator():

    print(value)

输出:

1

2

3

生成器与协程
生成器是实现协程的基础,可以通过 send() 方法在执行过程中动态传入数据。

示例:简单协程

def coroutine():

    print("Coroutine started")

    while True:

        value = yield

        print(f"Received: {value}")

co = coroutine()
next(co)  # 初始化协程

co.send("Hello")
co.send("World")

co.close()

输出:

Coroutine started

Received: Hello

Received: World

总结

生成器是 python 中强大的工具,设计初衷是为了简化迭代器的实现,并提供惰性计算能力。它的主要特点包括状态保存、惰性求值、单次遍历和支持动态值传递。生成器在处理大数据、流式数据以及协程编程中非常有用。通过生成器,我们可以高效、优雅地解决复杂的迭代问题,同时节省内存和提高代码的可读性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值