#迭代器
#什么是迭代器协议?
是指对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,否则引起一个stop Iteration异常,以终止迭代。并且不可逆。
对象1 -->提供一个next方法 --> 调用对象的next方法(对象.next) -->对象2 ...... --> 直到无法生成下一项,引起stopIteration异常,报错
#可迭代对象
遵循迭代器协议的对象
#协议
协议是一种约定,可迭代对象实现了迭代器协议,python的一些内部工具(如for、sum、min、max函数)使用迭代器协议访问对象
#修正一下之前的一些误区
前面的章节我们都认为,(字符串、列表、元组、字典、集合、文件对象)都为可迭代对象,其实不然
他们本身均不遵循迭代器协议,不信你试试 'list'.__next__()(报错) 他们并没有 next方法,故他们不是可迭代对象
那么问题来了,为什么我们能用for循环他们呢? for循环不是使用迭代器协议访问对象吗?
其实在使用for循环时,系统内部自动的进行了一个操作,即 对象1 = 对象.__iter__() ,这样对象1就为一个迭代器(可迭代对象)来代替原来的对象用于执行for循环
然后不断执行对象.__next__(),直到发现stopIteration异常,终止。
虽然如列表可用while循环通过索引的方式遍历所有元素,但无序的字典、集合则无法索引。于是for其实给各种类型提供了一个统一的遍历手段,
text = 'hello' for i in text : text_new = text.__iter__() --> 相当于 print(i) 里的一次迭代 print(text_new.__next__())
#用while循环模拟for循环
a = 'abcdf23' a1 = a.__iter__()
for i in a :
print(i)
#相当于 while True : try: #捕捉异常 print(a1.__next__()) except StopIteration : print('迭代完毕,循环终止') break
#iter函数
将符合迭代器协议的对象转换为迭代器,其第二个参数控制迭代到什么时候为止
1 a = ['a', 'b', 'c', 'd'] 2 def test(): 3 return a.pop() 4 #第二个参数'b'代表一直迭代直到值为'b'的时候停止 5 b = iter(test, 'b') 6 for i in b: 7 print(i)
小补充:
a.__next__ == next(a)
迭代器就是可迭代对象,值为地址的形式
#生成器
#生成器可以看做是一种数据类型,本身就遵循迭代器协议
#生成器是一种特殊的迭代器
#如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)
#生成器只能遍历一次
def iteration() : for i in range(1,6) : yield '迭代第%s次' %(i) a = iteration() for item in a : print(item) print(a.__next__()) -->StopIteration
#生成器函数
#yield语句(可被send赋值) , 代替return (返回一个生成器,相当于自动帮你完成了__iter__) ,并且可以返回多次
#可以保留函数的状态 执行函数 --> 碰上第一个yield 返回一个生成器 ,函数暂停--> 返回的生成器执行了一个 next方法 --> 函数从暂停位置(第一个yield处)继续执行,碰上第二个yield 返回一个生成器
#好处,函数每完成一部分的数据都可以马上进行下一步处理,而不是全部完成后再一起处理
def a() : for i in range(1,10) : print('开始迭代第%s次' %i) yield '第%s次迭代完毕' %i print('准备下一次迭代') b =a() print(b.__next__()) print(b.__next__()) print(b.__next__())
#send语句,除了有__next__()的进行下一次迭代的功能,还能给yield传值
def a() : for i in range(1,10) : print('开始迭代第%s次---->函数执行的结果' %i) res=yield '第%s次迭代完毕(这是返回值,函数外部print的结果)' %i print(res) b =a() print(b.__next__()) #第一次迭代只能传 None 因为它是传给上一次迭代的yield print(b.__next__()) #yield不用send传值默认为None print(b.send('准备下一次迭代'))
#三元表达式
name = 'wwj' res = 'beautiful girl' if name == 'wwj' else 'get out' print(res)
grade1 = ['成绩 = %s' %(i) for i in range(10) if i > 5] print(grade1)
#列表解析(简洁的生成列表)
grade = [] for i in range(10) : grade.append('成绩 = %s' %(i)) print(grade) #以下代码用列表解析来完成上文相同的功能 grade1 = ['成绩 = %s' %(i) for i in range(10)] print(grade1) #但是以上方法仍有不足,以上等于是将所有列表元素都加载到内存中,占用内存空间大 #以下用生成器表达式(把列表解析的[]换成()就行) grade1 = ( '成绩 = %s' %(i) for i in range(10) if i > 5 ) print(grade1)
#生成器表达式
#把列表转换为生成器,节省内存空间
l = [1,2,3,4,'sdfadasf',['asdfds']] generator = (i for i in l ) print(generator) while True : try : print(generator.__next__()) except StopIteration : print('迭代完毕') break
#小补充
l = [1,2,3,4] sum(l)
实际等于
list = [1,2,3,4] sum(i for i in list) #所以sum()函数默认帮我们做了一个转换为生成器的操作
生成器也能被for循环
def iteration() : for i in range(1,100) : yield '迭代第%s次' %(i) for item in iteration() : print(item)
#一个生成器的小例子,计算人口百分比(比直接获取整个人口普查文本然后放入列表中要节省内存空间的多)
#'人口普查'文本
{'地区':'北京' , '人口' : 10232142} {'地区':'新疆' , '人口' : 102322} {'地区':'青岛' , '人口' : 3223322}
#获取地区及其人口所占百分比
def population() : with open('人口普查','r',encoding='utf-8') as f : # num = 0 # for i in f : # num += eval(i)['人口'] num = sum(eval(i)['人口'] for i in f) #比上几行的代码简洁优美 f.seek(0) #此时生成器已经遍历完,需要seek返回文章开头重新遍历 for i in f : a = eval(i)['地区'] b = eval(i)['人口'] yield a,"人口百分比 %%%f"%(b/num*100) a = population() print(a.__next__()) print(a.__next__()) print(a.__next__())
#产生和处理同时完成(同时执行两个程序),单线程里的并发
def consumer(num): #获取产生的值,并进行处理 print('%s 开始照镜子' %num) while True: res=yield print('偷偷对%s使用变美喷雾 颜值+%d' %(num,res)) if res==5 : break def producer(name): #产生值 b = consumer('%s' % name) b.__next__() for i in range(1,6): try: b.send(i) except StopIteration : print('wwj说:哇,我怎么这么美') break producer('wwj')