从零开始:Python语言进阶之生成器

在Python编程的世界里,生成器是一个既基础又强大的功能,它就像是一个“数据加工厂”,能按需生产数据,帮助我们更高效地处理数据。接下来,我们就从最基础的概念开始,深入了解生成器。

 

一、生成器的基础概念

 

生成器是一种特殊的迭代器。那什么是迭代器呢?简单来说,迭代器是可以被 for 循环遍历的对象,比如列表、元组、字典等。但与普通迭代器不同,生成器不会一次性把所有数据都准备好,而是在需要的时候才生成一个数据,这种特性叫做惰性求值。

 

举个生活中的例子,假设有一个巨大的仓库装满了货物(大量数据)。普通的方式是一次性把所有货物都搬到外面(一次性生成所有数据),这会占用很大空间;而生成器就像是一个“按需发货员”,每次只拿出一件货物(每次生成一个数据),等你需要下一件时,它再去拿,这样就不会占用过多空间。在编程中,当我们处理大量数据,或者数据生成过程比较复杂时,生成器的这种特性就能大大节省内存,提高程序运行效率。

 

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

 

(一)生成器函数

 

1. 定义方式

生成器函数看起来和普通函数很像,唯一的区别是普通函数用 return 返回结果,而生成器函数用 yield 关键字返回结果。比如下面这个简单的生成器函数:

def simple_generator():
    print("生成器开始工作")
    yield 1
    print("继续生成数据")
    yield 2
    print("生成器完成任务")

2. 执行过程详解

我们通过调用这个函数来创建一个生成器对象:

gen = simple_generator()

此时,函数内部的代码并不会立即执行,直到我们调用 next() 函数来获取数据。

print(next(gen))  

执行这行代码时,生成器函数开始运行,先打印“生成器开始工作”,遇到 yield 1 后,函数暂停执行,把 1 返回给调用者。这时函数的状态被“记住”,包括所有变量的值和执行位置。

 

当我们再次调用 next() :

print(next(gen))  

生成器函数从上次暂停的地方继续执行,打印“继续生成数据”,遇到 yield 2 再次暂停,把 2 返回。如果再调用 next(gen) ,因为没有更多的 yield 语句,就会抛出 StopIteration 异常,表示数据生成结束。
 
3. 实际应用案例
以生成偶数数列为例:

def even_numbers(n):
    num = 0
    count = 0
    while count < n:
        yield num
        num += 2
        count += 1

使用这个生成器函数:

for num in even_numbers(5):
    print(num)

这样,每次循环只会生成一个偶数,而不是一次性生成包含5个偶数的列表,节省了内存。

 

(二)生成器表达式

 

1. 定义与语法

生成器表达式的语法和列表推导式很像,只不过列表推导式用方括号 [] ,生成器表达式用圆括号 () 。比如,生成1到10的平方数:

# 列表推导式,会立即生成列表
square_list = [x ** 2 for x in range(1, 11)]
# 生成器表达式,不会立即生成数据
square_generator = (x ** 2 for x in range(1, 11))

2. 与列表推导式的区别

列表推导式会马上把所有结果计算出来并放在一个列表里,占用相应的内存空间。例如上面的 square_list ,会一次性生成包含10个平方数的列表。

 

而生成器表达式只是一个“生产方案”,只有在使用 next() 函数或者通过 for 循环遍历它时,才会逐个生成数据。这就好比列表推导式是把所有产品都生产出来堆在仓库,而生成器表达式是接到订单才开始生产,大大减少了内存占用。

 

3. 使用方法

 

① 用 next() 函数获取数据:

gen = (x * 3 for x in range(3))
print(next(gen))  # 输出 0
print(next(gen))  # 输出 3
print(next(gen))  # 输出 6
# print(next(gen))  # 再次调用会抛出StopIteration异常

② 用 for 循环遍历:

gen = (x * 3 for x in range(3))
for num in gen:
    print(num)

三、生成器的常用操作方法

 

(一) next() 方法

 

 next() 方法是获取生成器数据的主要方式之一。每次调用 next() ,生成器就会从上一次暂停的地方继续执行,直到遇到下一个 yield 语句,返回对应的值。如果没有更多 yield 语句,就会抛出 StopIteration 异常。

 

(二) send() 方法

 

 send() 方法不仅能让生成器继续执行,还能给生成器“传值”。比如:

def greeting_generator():
    name = yield "你好"
    yield f"{name},最近好吗?"

gen = greeting_generator()
print(next(gen))  
print(gen.send("小明"))  

这里,第一次调用 next(gen) 时,生成器执行到 yield "你好" 暂停,返回“你好”。第二次调用 gen.send("小明") ,“小明”会作为 name 的值传入生成器内部,然后生成器继续执行,遇到下一个 yield ,返回 "小明,最近好吗?" 。

 

(三) throw() 方法

 

 throw() 方法可以在生成器内部抛出异常。比如我们想提前结束生成器:

def count_generator():
    num = 0
    while True:
        try:
            yield num
            num += 1
        except ValueError:
            print("收到异常,停止生成")
            break

gen = count_generator()
for _ in range(3):
    print(next(gen))
gen.throw(ValueError)

这里,前三次调用 next(gen) 正常生成数据,当调用 gen.throw(ValueError) 时,生成器内部捕获到 ValueError 异常,执行相应的处理逻辑后结束。

 

四、使用生成器常见的基础问题及解决办法

 

(一) StopIteration 异常处理

 

当生成器数据生成完毕,再调用 next() 就会抛出 StopIteration 异常。在使用 for 循环遍历生成器时,这个异常会被自动处理,循环会正常结束。但如果手动调用 next() ,就需要用 try - except 来处理异常:

gen = (x for x in range(3))
while True:
    try:
        value = next(gen)
        print(value)
    except StopIteration:
        break

(二)生成器的状态混乱

 

因为生成器会暂停和恢复执行,在复杂的代码里,可能会出现变量状态不对的情况。比如一个生成器函数里有多个 yield ,变量在不同 yield 之间的更新没处理好。解决办法是把生成器函数的逻辑写得尽量简单清晰,给变量和关键代码加上注释,标明每次 yield 前后变量的变化。

 

(三)生成器与其他数据类型配合使用

 

有时候我们需要把生成器的数据转成列表、字典等其他数据类型。比如用 list() 函数可以把生成器转成列表:

gen = (x for x in range(5))
result_list = list(gen)
print(result_list)

但要注意,如果生成器数据量特别大,转成列表可能会占用大量内存,这时要谨慎使用,或者考虑分批处理数据。

 

掌握生成器的这些基础知识和使用技巧,能让我们在Python编程中更灵活地处理数据。无论是处理大量数据的场景,还是需要按需生成数据的情况,生成器都能成为我们编程的得力助手。

正在持续更新,感谢您的关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玉笥寻珍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值