python编程之生成器

什么是生成器

在Python中,生成器(generator)是一种特殊的迭代器,它可以动态地生成值,而不是一次性将所有值存储在内存中。生成器使用yield关键字来产生值,每次调用生成器的__next__()方法(或者直接使用next(generator)函数)时,生成器会执行,直到遇到一个yield语句,然后返回yield语句后面的值,并暂停执行。下次调用时,生成器会从上次暂停的地方继续执行。

生成器的一个常见用法是在处理大量数据或者无限序列时,节省内存开销。因为它们是惰性计算的,只有在需要时才会生成值。

声明一个生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_num(number):  # 生成器函数
index = 0
while index < number:
yield index # yield可以返回一个结果并且不会终止一个函数的运行
index += 1


# 生成器函数不能直接使用小括号的方式获取结果
# 生成器函数调用后返回的是一个生成器对象
print(get_num(5).__next__())

# 生成器对象中的结果可以使用next函数进行获取,和迭代器类似
obj = get_num(5)
print(next(obj))
print(next(obj))
print(next(obj))

在python中使用yield关键字来定义一个生成器函数,在上面的例子中get_num函数声明了yield关键字,如果我们要打印get_num函数返回的值不能直接使用get_num(5),get_num函数是一个生成器函数,它返回的是一个生成器对象,生成器对象是一种特殊的迭代器,所以需要调用迭代器对象的next()方法来获取生成器函数的返回值。

生成器的运行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_num(number):
print('开始迭代...')
i = 0
while i < number:
print('迭代中...')
yield i
print('迭代结束')
i += 1


obj = get_num(5)
print(next(obj))
print(next(obj))

"""
打印结果为:
开始迭代...
迭代中...
0
迭代结束
迭代中...
1
"""

在上一段代码中定义了一个生成器函数,在执行生成器函数的时候也是和普通函数一样从上往下执行,第一次调用函数的时候满足while循环条件进入到循环体内,生成器函数第一次遇到yield将结果返回出去,并保存当前的运行状态[保存了代码运行的位置],第二次运行则根据上一次运行的位置继续向下执行。

生成器表达式

1
nums = (i for i in range(1, 11))

上面这段代码就是生成器表达式,那么为什么要使用生成器表达式呢?
假设现在我们需要生成100万条数据,我们使用列表推导式生成的话会很慢,因为列表推导式生成100万条数据的时候会一次性把所有数据全部生成,数据没有生成完程序是不会退出的,而生成器表达式并不会直接给你生成数据,而是给你返回一个生成器对象,这个对象占用的内存并不大,生成器具有懒加载机制,调用next方法才可以获取到结果。

生成器的send和close方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def get_num(number):
i = 0
while i < number:
data = yield i
if data == '这是测试时传递的一个值':
print(123)
print('data变量的值:', data)
i += 1


obj = get_num(5)
# print(next(obj))
print(obj.send(None)) # 第一次执行生成器时不能使用send方法去传递任何对象,除了None对象
print(obj.send('这是测试时传递的一个值')) # 可以在运行生成器对象的过程中传递一个值

obj.close() # 关闭生成器对象,后续代码无法运行生成器对象
print(next(obj))

"""
运行结果为:
0
123
data变量的值: 这是测试时传递的一个值
1
"""

生成器的send方法可以给生成器发送一个信号,在上面的实例中我们让data = yield i,在第一次执行生成器函数时执行到yield i程序会停止并返回一个生成器对象,此时还没有声明data变量,所以此时我们的内存中并没有加载data这个变量,如果此时调用send方法传递一个值会出现报错,但是传递None对象除外,我们调用send方法传递一个值之前会调用一次next方法,随后就会将send方法中的参数值传递给我们生成器对象。close方法用来关闭生成器对象,生成器对象一旦关闭,后续代码无法运行生成器对象。