前言
这节内容主要介绍生成器、如何创建生成器、生成器与普通函数的区别、生成器推导、生成器与迭代器的区别、生成器在实战中的应用
生成器
我们知道,函数体包含 yield 关键字的函数不是一个普通函数。这种函数叫做 生成器 ( generator )。
创建生成器
生成器创建和函数一样。我们举个例子
示例1
还记得在迭代器那片文章中,我们使用迭代器实现斐波那契数列,这里我们使用生成器实现
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
f = fib()
for i in range(10):
print(next(f))
'''
0
1
1
2
3
5
8
13
21
34
'''
这个 fib 函数是一个生成器函数,它使用两个变量 a 和 b 来保存当前斐波那契数列中的两个相邻数字。在每次迭代中,它使用 yield 关键字产生当前数字 a,然后更新 a 和 b 的值,以继续生成下一个数字。这段代码将生成前 10 个斐波那契数列的数字并打印出来。
生成器与普通函数区别
生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。
注意:有人要问了,是如何被重新唤醒的呢?就是next()函数,next 返回本次结果,并暂停在 yield。
生成器推导
看这段代码,你一定熟悉
l = [i**2 for i in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
这段代码生成前10个数字的平方,也就是列表推导式。
那什么是生成器推导呢?其工作原理与列表推导相同,但不是创建一个列表(即不立即执行循环),而是返回一个生成器,让你能够逐步执行计算。像下面这样
l = (i**2 for i in range(10))
print(l) # <generator object <genexpr> at 0x1099065f0>
print(next(l)) # 0
相信你已经看到了区别,不同于列表推导,这里使用的是圆括号。
递归式生成器
递归式生成器是基于递归算法的生成器。它通过在生成器函数内部调用自身来生成序列中的下一个元素。递归式生成器在生成每个元素时依赖前一个元素,从而实现递归的效果。
示例1
使用递归式生成器实现斐波那契数列
def fibonacci_recursive(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
def fibonacci_generator_recursive():
n = 0
while True:
yield fibonacci_recursive(n)
n += 1
在这个例子中,fibonacci_recursive 函数是一个递归函数,用于计算斐波那契数列中第 n 个数字。fibonacci_generator_recursive 函数是一个递归式生成器函数,通过调用 fibonacci_recursive 来生成斐波那契数列。
你可以使用以下代码来打印前 10 个斐波那契数列的数字:
fib = fibonacci_generator_recursive()
for i in range(10):
print(next(fib))
这样就可以使用递归式生成器生成斐波那契数列的序列。
实战
实战一
文件处理:当需要处理大型文件时,逐行读取并一次性加载整个文件到内存可能会导致内存溢出。这时可以使用生成器来逐行读取文件,每次只加载一行到内存中,并通过生成器返回给调用者。这样可以有效地减少内存占用,并且在处理大型文件时提高性能。
def read_file_line_by_line(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
# 使用生成器逐行读取文件并处理每一行
file_generator = read_file_line_by_line('example.txt')
for line in file_generator:
# 在这里对每一行进行处理
print(line)
实战二
数据流处理:在数据流处理的场景中,数据往往是连续不断地产生或传输的。生成器可以用于处理这些连续的数据流,通过生成器函数逐个生成数据,并在需要的时候进行处理。这样可以避免一次性加载所有数据,而是按需处理和生成,提高处理效率
def process_data_stream(data_stream):
for data in data_stream:
# 在这里对数据进行处理
processed_data = process(data)
yield processed_data
# 模拟数据流,例如从网络接收实时数据
data_stream = receive_data_stream()
processed_data_generator = process_data_stream(data_stream)
for data in processed_data_generator:
# 在这里处理已处理的数据
print(data)
实战三
实时日志分析:在实时日志分析的应用中,日志通常是不断产生的,需要对日志进行实时处理和分析。生成器可以用于读取日志文件或接收实时日志流,并逐行生成日志数据记录。通过生成器可以实现高效的实时日志分析,同时节省内存消耗。
def analyze_logs(log_file_path):
with open(log_file_path, 'r') as log_file:
for log_line in log_file:
log_data = parse_log(log_line)
yield log_data
# 对实时日志进行分析
log_generator = analyze_logs('logs.txt')
for log in log_generator:
# 在这里处理每条分析后的日志
print(log)
生成器与迭代器区别
1、实现方式:生成器是通过函数来创建的,使用关键字 yield 来产生一个值,并在需要时暂停执行。而迭代器是通过类来创建的,实现了 __iter__() 和 __next__() 方法,用于返回下一个元素。
2、内存占用:生成器只在需要时才会生成和返回一个元素,因此在内存上只存储当前生成的元素以及生成器的状态信息。而迭代器可能需要在内存中保留整个数据集,尤其是当使用列表等容器类型作为迭代器时。
3、数据生成方式:生成器可以通过循环、递归等方式动态生成数据,因此对于无限序列或者大型数据集非常适用。而迭代器通常是基于已经存在的数据集合进行迭代。
最后
到这里生成器应该了解差不多了,最后我们在说一下生成器是否可以遍历多次呢?
对于一个有限元素的生成器,只可以遍历一次,看下面这个例子
def number_generator():
num = 1
while num<10:
yield num
num += 1
generator = number_generator()
print(list(generator)) # 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(generator)) # 输出: []
可以看到,再次输出时,为空列表,如果是使用next()返回当前结果,生成器消耗完后,next会报StopIteration的错误。