Python中的生成器是一种特殊的迭代器,它不需要像列表那样一次性把所有元素加载到内存中,而是通过惰性计算的方式逐个生成元素,在内存优化场景中有着不可替代的作用。

生成器的基本定义与创建方式
生成器本质上是一个返回迭代器的函数,或者是一个生成器表达式,它的核心特点是执行过程中可以暂停和恢复,不会一次性生成所有结果。常见的创建方式有两种:
1. 使用yield关键字定义生成器函数
当函数中包含yield语句时,这个函数就不再是一个普通函数,而是一个生成器函数,调用生成器函数会返回一个生成器对象,而不是直接执行函数体。
# 定义一个简单的生成器函数,生成1到n的整数
def number_generator(n):
i = 1
while i <= n:
yield i # 每次执行到yield就会暂停,返回当前i的值
i += 1
# 调用生成器函数得到生成器对象
gen = number_generator(5)
# 生成器是迭代器,可以通过next()函数获取下一个元素
print(next(gen)) # 输出1
print(next(gen)) # 输出2
# 也可以用for循环遍历生成器
for num in gen:
print(num) # 输出3、4、5
2. 使用生成器表达式
生成器表达式的语法和列表推导式类似,只是把方括号换成圆括号,它返回的是一个生成器对象,而不是一个列表。
# 列表推导式,一次性生成所有元素
list_data = [x * 2 for x in range(5)]
print(list_data) # 输出[0, 2, 4, 6, 8]
# 生成器表达式,惰性生成元素
gen_data = (x * 2 for x in range(5))
print(gen_data) # 输出<generator object <genexpr> at 0x...>
# 遍历生成器表达式
for item in gen_data:
print(item) # 输出0、2、4、6、8
yield关键字的工作逻辑
yield是生成器的核心关键字,它的作用和return类似,都可以返回值,但二者有本质区别:
- return执行后会直接结束函数的运行,函数内的所有状态都会销毁
- yield执行后会暂停函数的运行,保存当前的执行状态,下次调用next()或者遍历的时候会从暂停的位置继续执行
我们可以通过下面的例子更直观地理解yield的执行流程:
def yield_demo():
print("开始执行生成器函数")
yield "第一个值"
print("恢复执行,准备返回第二个值")
yield "第二个值"
print("恢复执行,准备返回第三个值")
yield "第三个值"
print("生成器函数执行结束")
gen_obj = yield_demo()
print(next(gen_obj)) # 输出:开始执行生成器函数 第一个值
print(next(gen_obj)) # 输出:恢复执行,准备返回第二个值 第二个值
print(next(gen_obj)) # 输出:恢复执行,准备返回第三个值 第三个值
生成器在内存优化中的作用
生成器的核心优势就是内存优化,它不会一次性把所有元素都加载到内存中,只有在需要的时候才会生成对应的元素,特别适合处理大量数据或者无限序列的场景。
普通列表和生成器的内存占用对比
我们可以通过sys模块的getsizeof函数查看对象的内存占用,对比列表和生成器的差异:
import sys
# 生成一个包含100万个整数的列表
list_nums = [i for i in range(1000000)]
# 生成一个包含100万个整数的生成器
gen_nums = (i for i in range(1000000))
print(f"列表的内存占用:{sys.getsizeof(list_nums)} 字节")
print(f"生成器的内存占用:{sys.getsizeof(gen_nums)} 字节")
运行上述代码可以看到,列表的内存占用会随着元素数量的增加而线性增长,100万个整数的列表大约占用8MB左右的内存,而生成器的内存占用几乎不会随元素数量变化,始终只有几十字节,内存优化效果非常明显。
适用场景举例
生成器非常适合以下场景:
- 处理大文件:比如读取一个几十GB的日志文件,不需要一次性把文件内容全部读到内存中,可以用生成器逐行读取
- 生成无限序列:比如生成斐波那契数列,不需要限制长度,生成器可以按需生成下一个值
- 复杂计算的惰性执行:比如需要计算一系列复杂的结果,但是不一定会全部用到,用生成器可以避免不必要的计算
下面是一个逐行读取大文件的生成器示例:
def read_large_file(file_path):
with open(file_path, "r", encoding="utf-8") as f:
while True:
line = f.readline()
if not line:
break
yield line.strip()
# 使用生成器逐行处理文件,不会一次性加载整个文件到内存
for line in read_large_file("large_log.txt"):
# 处理每一行内容
pass
生成器的注意事项
使用生成器的时候需要注意以下几点:
- 生成器是单向迭代的,只能遍历一次,遍历结束后再次调用next()会抛出StopIteration异常
- 生成器不支持索引访问和len()函数,因为它没有把所有元素存储在内存中,无法提前知道元素总数
- 如果需要在生成器中传入值或者抛出异常,可以使用生成器的send()和throw()方法,但日常简单场景用yield就足够
生成器是Python中非常实用的特性,合理运用可以在处理大量数据的时候大幅降低内存占用,提升程序的运行效率,是Python开发者必须掌握的基础技能之一。