生成器简介:
通过列表生成式(列表推导式),我们可以直接创造一个列表。但是,受到内存限制,列表容量肯定是有限的。 而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,要是我们仅仅需要访问前面几个元素, 那后面绝大多数元素占用的空间都会被白白浪费了。所以,如果列表元素可以按照某种算法推导出来,那我 们就可以在循环的过程中不断推算出后续的元素。这样就不必创建完整的list,从而节省大量的空间。在Python 中,这种一边循环一边计算的机制,称为生成器:generator
得到生成器方式:
1.通过列表推导式的方式得到生成器,将外部中括号换为括号。
2.借助函数完成,函数中存在yield,该函数就是一个生成器。
生成器的使用:
一个列表推导式:
newlist = [x + 1 for x in range(10)]
print(type(newlist))
print(newlist)
输出:
<class 'list'>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
仅仅将中括号换为括号。
g = (x + 1 for x in range(10))
print(type(g)) # 一个生成器对象,可迭代
print(g)
输出:
<class 'generator'>
<generator object <genexpr> at 0x0000028FC47E5AC0>
可以看到,得到了一个生成器。
生成器,可以进行迭代,当迭代到里面没有元素时会报错:StopIteration
生成器可以借助以下方式进行迭代:
next(生成器名) #builtins 系统内置函数
生成器名._next_() #生成器内部方法
g = (x * 3 for x in range(10))
while True:
try:
e = next(g)
s = g.__next__()
print(e)
print(s)
except:
print('生成器的元素已迭代完毕,没有更多元素了!')
break
输出:
0
3
6
9
12
15
18
21
24
27
生成器的元素已迭代完毕,没有更多元素了!
可见,next()和_next_()可以对生成器进行迭代。且迭代完毕后,再迭代会报错。
用函数定义生成器:
#只要函数里出现了yield关键字,说明函数就不是函数 了,变成了一个生成器对象
步骤:
1.定义一个函数,里面有yield关键字,它决定了函数运行在哪里暂停
2.调用函数,接收调用的结果,结果即为生成器
3.借助next()和_next()_,得到元素
###举例说明###
def func():
n = 0
while True:
n += 1
print(n)
yield n # 动作: return n + 暂停(保留现场)
print('JJ')
g = func()
print(g) # g为生成器对象
print(next(g)) # 调用next()时,程序进入了func()执行代码,直到遇到yield,把n返回并退出函数
print(next(g)) # 第二次调用时,其实是从yield下方开始执行代码
1.定义函数后,用一个变量接住调用函数后的返回值,该变量就变成了一个生成器。
2.第一次迭代该生成器的时候,相当于执行了func()函数,并且当执行到yield时,函数暂停,返回n的值。
3.第二次迭代生成器的时侯,函数从yield的下一行开始执行,经while True循环,再到yield 返回n并暂停。
不用递归,得到斐波那契数列:
其中,用到了生成器:
def fib():
i,j = 0,1
while True:
yield i
i,j = j,i+j
fi = fib() #fi是一个可以产生Fibonacci数列的生成器
for i in range(8):
print(fi.__next__())
输出:
0
1
1
2
3
5
8
13
yield关键字和send(value):
yield是产生的意思,send是递送的意思,可简单理解为yield是生成器向外产生元素,而send是向生成器里递送值的行为。
###举例说明###
def gen():
i = 0
print('jsy')
while i<5:
temp =yield i #先执行关键字操作,返回i,暂停代码;下次再执行时,send给yield一个返回值'你好'
print('temp:',temp)
i +=1
return '没有更多的数据!'
g = gen()
print(g.send(None)) #第一次使用生成器时,不能向生成器传入非空的值(必须空值)
n = g.send('你好')
print(n)
print(next(g))
print(next(g))
print(g.__next__())
输出:
jsy
0
temp: 你好
1
temp: None
2
temp: None
3
temp: None
4
注意:在第一次迭代时不可以使用send向生成器里传非空值(可以传空值),因为是生成器的第一 次启动。
生成器第一次启动时,可用send(None)或next()或_next_()。
生成器的应用:
Python里引入了协程的概念,进程>线程>协程。
生成器可在协程中应用,实现线程中多任务的切换。
#例如:迅雷下载文件,可将文件切块分配给多个线程下载,从而使下载速度加快 当一个线程里有多任务时,引入协程
def task1(n):
for i in range(n):
print('正在下载第{}个”文件“'.format(i))
yield None
def task2(n):
for i in range(n):
print('正在吃第%s碗饭'%(i))
yield None
# task1(5)
# task2(5)
#实现函数打印顺序交替执行
g1 = task1(5)
g2 = task2(5)
while True:
try:
g1.__next__()
g2.__next__()
except:
break
输出:
正在下载第0个”文件“
正在吃第0碗饭
正在下载第1个”文件“
正在吃第1碗饭
正在下载第2个”文件“
正在吃第2碗饭
正在下载第3个”文件“
正在吃第3碗饭
正在下载第4个”文件“
正在吃第4碗饭
迭代器:
1.迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。
2.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
3.迭代器只能往前不会后退。
4.可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
5.生成器是可迭代的,同时也是迭代器的一种
6.可迭代的不一定是迭代器,迭代器一定可迭代
###如何判断一个对象是可迭代的?
isinstance()
from collections.abc import Iterable
from collections.abc import Iterator #导入后可用来检测是否是迭代器
list1 = [1,2,5,3,6]
print(isinstance(list1,Iterable)) #判断是否是可迭代的
print(isinstance('ass',Iterable))
print(isinstance('ass',Iterator))
print(isinstance(list1,Iterator))
print(isinstance(15,Iterable)) #整型不可迭代
# print(next(list1)) #报错,列表不是迭代器
输出:
True
True
False
False
False
注意:使用isinstance()可验证出列表,集合,字典,元组是可迭代的。但是它们不能使用next(), 可知它们不是迭代器。
还可以通过collection中的Iterator类型判断:isinstance(' ',Iterator)
###使用iter()将可迭代对象转化为迭代器
list1 = [1,2,3,4,5]
list1 = iter(list1)
print(list1) #利用iter转换为迭代器
print(next(list1))
print(list1.__next__())
输出:
<list_iterator object at 0x000002AF077F3C40>
1
2
生成器和迭代器(放在一起看):
生成器:
1.生成器是迭代器的一种,在使用函数来创建生成器时,函数每调用便可产生一个迭代器(生成器),所以生成器可返回迭代器。
2.在使用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行,所以具有高效性,简洁性,节省内存;且可通过yield关键字位置的变化来完成不同功能的实现,添加了一分灵活性。
3.生成器更多借函数来实现功能。
迭代器:
1.一个迭代器对象必须是定义了__iter__()方法和next()方法的对象。
2.节约内存(循环过程中,数据不用一次读入,在处理文件对象时特别有用,因为文件也是迭代器对象)
3.不依赖索引取值、实现惰性计算(需要时再取值计算)。