生成器(generator)
什么是生成器?
- 简单的来说:一边运行一边生产数据的方式
- 生成器是一类特殊的迭代器
- 特点:数据真正需要的时候再生产,而不是先生成N个数据放在那里等着用
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。 而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素, 那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来, 那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
制作生成器的两种方式
(一)
将列表推导式 [ ] 改成==()==
t = [i for i in range(100)]
print(type(t)) # <class 'list'>
t1 = (i for i in range(100))
print(type(t1)) # <class 'generator'>
(二)
在def定义的函数中用yield。
def test1():
print(1)
print(2)
print(3)
return 123
def test2():
print("----4----")
yield 4
print("----5----")
num = yield
print("----6----num=", num)
yield
return 466
t1 = test1() # 会执行test1函数中的代码
t2 = test2() # 不会执行test2中的代码,而是将test2()认为是创建了一个对象
print(type(t1)) # <class 'int'>
print(type(t2)) # <class 'generator'> 这就是生成器
只要在def中有yield关键字的 就称为 生成器,可以用next()来获取生成器的数据,生成器是可以迭代的。
def gen():
i = 0
yield i
i += 1
num = yield 200
print(num)
i += 1
yield 100
g = gen()
print(next(g)) # 0
print(next(g)) # 200
我们除了可以使用next()函数来唤醒生成器继续执行之外,还可以使用send()函数来唤醒执行。
send唤醒作用:可以在唤醒前的同时向段点传入一个附加数据
def gen():
i = 0
while i < 5:
temp = yield i
print(temp)
i += 1
g = gen()
print(next(g)) # 0,打印完不动了卡在yield中
# 打印完0之后卡在第一次yield中这是send传参给temp,所以打印完20 i+1,
# 又进入循环卡到yield,yield返回i值所以打印1,此时程序又卡在第二次的yield上。
print(g.send(20)) # 20,1
# 打印完1之后卡在第二次yield中这是send传参给temp,所以打印完12 i+1,
# 又进入循环卡到yield,yield返回i值所以打印2,此时程序又卡在第三次的yield上。
print(g.send(12)) # 12,2
#这块有点难理解,需要慢慢一步一步细心走!!!
总结
- 使用了yield关键字的函数就不再是函数,而是生成器。
- yield关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起。
- 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
- 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
- Python3中的生成器可以使用return返回最终运行的返回值
- 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
- 生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:存储的是生成数据的方式(即算法),而不是存储生成的数据,因此节约内存