一、什么是迭代器协议?
对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stopiteration异常,以终止迭代(只能往后走不能往前进)
可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个iter()方法)
协议是一种约定,可迭代对象实现了迭代器协议,Python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
Python中强大的for循环机制
例如字符串、列表、元组、字典、集合、文件对象等都不是可迭代对象,只不过在for循环时,调用了他们内部的_iter_方法,把他们变成了可迭代对象
然后for循环调用可迭代对象的_next_方法去取值,而且for循环会捕捉Stopiteration异常,以终止迭代。
含有__iter__方法的就是可迭代对象
#__iter__()生成可迭代对象,返回一个对象地址。之后利用__next__()进行遍历 x='hello' iter_test=x.__iter__() print(iter_test) -----> <str_iterator object at 0x000001AEE54225C0> print(iter_test.__next__()) h print(iter_test.__next__()) e print(iter_test.__next__()) l print(iter_test.__next__()) l print(iter_test.__next__()) 0 l=[1,2,3] for i in l: # i_l=l.__iter__() i_l.__next__() print(i) ====>> 1 2 3 #__iter__()遵循迭代器协议,生成可迭代对象 iter_l=l.__iter__() print(iter_l.__next__()) print(iter_l.__next__()) =====>> 1 2 #不使用for循环,完成元组、列表等等的遍历,while不能进行字典集合的遍历 l=[1,2,3,4] index=0 while index<len(l): print(l[index]) index+=1
#迭代器即迭代的工具,那什么是迭代呢? #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值 while True: #只是单纯地重复,因而不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器 #2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象? 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象 文件类型是迭代器对象 open('a.txt').__iter__() open('a.txt').__next__() #4、注意: 迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
三 、迭代器对象的使用
三 迭代器对象的使用 dic={'a':1,'b':2,'c':3} iter_dic=dic.__iter__() #得到迭代器对象,迭代器对象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身 iter_dic.__iter__() is iter_dic #True print(iter_dic.__next__()) #等同于next(iter_dic) print(iter_dic.__next__()) #等同于next(iter_dic) print(iter_dic.__next__()) #等同于next(iter_dic) # print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志 #有了迭代器,我们就可以不依赖索引迭代取值了 iter_dic=dic.__iter__() while 1: try: k=next(iter_dic) print(dic[k]) except StopIteration: break #这么写太丑陋了,需要我们自己捕捉异常,控制next,python这么牛逼,能不能帮我解决呢?能,请看for循环
四 、for循环
#基于for循环,我们可以完全不再依赖索引去取值了 dic={'a':1,'b':2,'c':3} for k in dic: print(dic[k]) #for循环的工作原理 #1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic #2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码 #3: 重复过程2,直到捕捉到异常StopIteration,结束循环
五 、迭代器的优缺点
#优点: - 提供一种统一的、不依赖于索引的迭代方式 - 惰性计算,节省内存 #缺点: - 无法获取长度(只有在next完毕才知道到底有几个值) - 一次性的,只能往后走,不能往前退
六 生成器初探
一 什么是生成器?
可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象
插入知识点:
(三元表达式:'SB' if name == 'hello' else 'handsome')
生成器在Python中的表现形式:(Python有两种不同的方式提供生成器)
1.生成器函数:常规函数定义,但是使用yield语句而不是return语句返回结果 yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
2.生成器表达式:类似于列表缩写,但是生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
yield:
1).把函数做成迭代器
2).对比return,可以返回多次值,可以挂起/保存函数的运行状态
为何使用生成器之生成器优点?
#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码 def func(): print('====>first') yield 1 print('====>second') yield 2 print('====>third') yield 3 print('====>end') g=func() print(g) #<generator object func at 0x0000000002184360>
生成器和列表解析的区别:
生成器格式最外层是一个(),列表解析格式最外层是一个[]
生成器是将数据不用一次读取,而列表解析是一次读取所有数据(耗内存)
生成器返回一个生成器对象(不能直接循环),列表解析返回的是一个新列表
生成器好处:可以存储一次用一次,而不是全部产生后在用,可以在__next__()中间加一些代码
列表解析缺点:
缺点一:占空间大
缺点二:效率低
列表解析(一次生成所有,然后使用)
#列表解析 egg_list=[] for i in range(10): egg_list.append('鸡蛋%s' %i) print(egg_list) l = ["鸡蛋%s" %i for i in range(10)] print(l) =======>>以上两者功能是相同的,但第二种代码少,一次性生成10个蛋在内存中 li = ['鸡蛋%s' %i for i in range(10) if i>5] print(li) =====>> ['鸡蛋6','鸡蛋7','鸡蛋8','鸡蛋9']
生成器函数(生成器好处是可以存储一次用一次,而不是全部产生后在用)
#生成器函数用法 def product_baozi(): for i in range(100): print("正在生产包子") yield '一提包子%s' %i print('开始卖包子') pro_g=product_baozi() print(pro_g.__next__()) print(pro_g.__next__()) print(pro_g.__next__()) print(pro_g.__next__())
#本题实现人口普查功能,找总人口 def get_population(): with open('xxx_new','r',encoding='utf-8') as f: for i in f: yield i g = get_population() #实现输出总人口功能(多行实现) res=0 for p in g: p_dic=eval(p) # print(p_dic['population']) res+=p_dic['population'] print(res) #两行实现输出总人口功能 all_pop=sum(eval(i)['population'] for i in g) print(all_pop)
三 练习
1、自定义函数模拟range(1,7,2)
2、模拟管道,实现功能:tail -f access.log | grep '404'
#题目一: def my_range(start,stop,step=1): while start < stop: yield start start+=step #执行函数得到生成器,本质就是迭代器 obj=my_range(1,7,2) #1 3 5 print(next(obj)) print(next(obj)) print(next(obj)) print(next(obj)) #StopIteration #应用于for循环 for i in my_range(1,7,2): print(i) #题目二 import time def tail(filepath): with open(filepath,'rb') as f: f.seek(0,2) while True: line=f.readline() if line: yield line else: time.sleep(0.2) def grep(pattern,lines): for line in lines: line=line.decode('utf-8') if pattern in line: yield line for line in grep('404',tail('access.log')): print(line,end='') #测试 with open('access.log','a',encoding='utf-8') as f: f.write('出错啦404\n')
四 协程函数
#yield关键字的另外一种使用形式:表达式形式的yield
def eater(name):
print('%s 准备开始吃饭啦' %name) food_list=[] while True: food=yield food_list print('%s 吃了 %s' % (name,food)) food_list.append(food) g=eater('egon') g.send(None) #对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g) g.send('蒸羊羔') g.send('蒸鹿茸') g.send('蒸熊掌') g.send('烧素鸭') g.close() g.send('烧素鹅') g.send('烧鹿尾')
五 练习
1、编写装饰器,实现初始化协程函数的功能
2、实现功能:grep -rl 'python' /etc
#题目一: def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper @init def eater(name): print('%s 准备开始吃饭啦' %name) food_list=[] while True: food=yield food_list print('%s 吃了 %s' % (name,food)) food_list.append(food) g=eater('egon') g.send('蒸羊羔') #题目二: #注意:target.send(...)在拿到target的返回值后才算执行结束 import os def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper @init def search(target): while True: filepath=yield g=os.walk(filepath) for dirname,_,files in g: for file in files: abs_path=r'%s\%s' %(dirname,file) target.send(abs_path) @init def opener(target): while True: abs_path=yield with open(abs_path,'rb') as f: target.send((f,abs_path)) @init def cat(target): while True: f,abs_path=yield for line in f: res=target.send((line,abs_path)) if res: break @init def grep(pattern,target): tag=False while True: line,abs_path=yield tag tag=False if pattern.encode('utf-8') in line: target.send(abs_path) tag=True @init def printer(): while True: abs_path=yield print(abs_path) g=search(opener(cat(grep('你好',printer())))) # g.send(r'E:\CMS\aaa\db') g=search(opener(cat(grep('python',printer())))) g.send(r'E:\CMS\aaa\db')