python ---迭代器与生成器
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python ---迭代器与生成器相关的知识,希望对你有一定的参考价值。
一、什么是迭代?
迭代通俗的讲就是一个遍历重复的过程。
维基百科中 迭代(Iteration) 的一个通用概念是:重复某个过程的行为,这个过程中的每次重复称为一次迭代。具体对应到Python编程中就是,对于一个可迭代对象,比如Python中的list、tuple、string、dictionary,set等,使用某种循环结构来遍历其中的元素,这种遍历就是迭代。
1 #对列表进行遍历! 2 3 l=[‘a‘,‘b‘,‘c‘,‘d‘,‘e‘] 4 5 #while循环的方式 6 i=0 7 while i < len(l): 8 print(l[i]) 9 i+=1 10 11 #for 循环的方式 12 for i in range(len(l)): 13 print(l[i])
二、迭代器
1、迭代器定义
首先先明确 可迭代对象
在现所学的数据类型中,只有 文件 是迭代器,其他的数据类型:元组,字符串,字典,集合,列表都是 可迭代对象。
判断是否可迭代:只要判断对象本身是否内置了_iter_方法,那它就是可迭代的。
1 #可迭代的:只要对象本身有__iter__方法,那它就是可迭代的 2 d={‘a‘:1,‘b‘:2,‘c‘:3} 3 d.__iter__ #iter(d)
可迭代对象实现了 __iter__ 和 __next__ 方法,这两个方法对应内置函数 iter() 和 next() 。__iter__ 方法返回可迭代对象本身,这使得他既是一个可迭代对象同时也是一个迭代器。
1 #执行对象下的__iter__方法,就会得到一个返回值,得到的返回值就是迭代器。 2 # 使用next()就能依次取出迭代器中的值(在迭代器元素个数之内,超出会报错,也是一次性的元素,不可重复取值),这样就不用再依赖下标的方式。 3 d={‘a‘:1,‘b‘:2,‘c‘:3} 4 d.__iter__ #iter(d) 5 6 i=d.__iter__() #返回值,迭代器 7 8 print(i.__next__()) #print(next(i)) 9 print(i.__next__()) 10 print(i.__next__()) #3个元素,依次取三次,由于迭代器中的元素是一次性的,超出个数会报错! 11 print(i.__next__())
执行结果如下:
a b c Traceback (most recent call last): File "H:/迭代器.py", line 29, in <module> print(i.__next__()) StopIteration
迭代器
迭代器是访问集合中元素的一种方式,从集合中的第一个元素开始访问,直到所有的元素都被访问一遍后结束。迭代器不能回退,只能往前进行迭代。
迭代器提供了一个统一的访问的接口,只要是定义了iter()方法的对象,就可以使用迭代器进行访问。只要可以进行访问,就能被next()方法调用并不断返回下一个值的对象称为迭代器。换句话说,迭代器对象具有next()方法。
对于迭代器的理解,可以把迭代器看成是一个数据流,迭代器对象被next()函数调用不断返回下一个数据,直到没有数据时抛出StopIteration错误。
把这个数据流看做是一个有序序列,但却不能提前知道这个数据流到底有多长,而对于list、tuple等可迭代对象来说,对象的长度是可知的。这也是可迭代对象和迭代器的区别所在。
StopIteration异常:是迭代终止的信号,迭代器内的内容是有限的,迭代器内元素已全部取完,及取值结束再继续取的话,会报错。
异常捕捉:迭代器正常执行代码的过程中,print(next(*)) *代表迭代器 会抛出异常。为保证代码的正常运行,此时就用到了 try 和 except。
1)while:循环
1 l=[‘a‘,‘b‘,‘c‘,‘d‘,‘e‘] 2 i=l.__iter__() 3 while True: 4 try: #监听代码是否会报异常 StopIteration 5 print(next(i)) 6 except StopIteration: #判断异常是否为 StopIteration,是break 7 break
执行结果如下:
1 a 2 b 3 c 4 d 5 e
2)for 循环 :for 循环本质就是内部封装了迭代器,对 可迭代的对象 进行遍历取值,同时会在遇到异常捕获的时候会自行处理,所以for 循环作用在迭代器上不报错。
1 d={‘a‘:1,‘b‘:2,‘c‘:3} 2 print(d.__iter__()) 3 for k in d: #d.__iter__() 4 print(k)
执行结果如下:
1 #集合及文件 for 循环 迭代形式 2 s={1,2,3,4} 3 for i in s: 4 print(i) 5 #a.txt ="aaaaaa bbb cccc eee ffffff" 6 with open(‘a.txt‘,‘r‘) as f: 7 for line in f: 8 print(line.strip())
执行结果如下:
3)此处注意一个小点:关于文件的认知! 由于文件本身就是一个迭代器,当给文件添加_iter_函数的时候,对其迭代器本身是不冲突的!
1 f=open(‘a.txt‘,‘r‘) 2 print(f) 3 print(f.__iter__()) 4 #迭代器执行iter,得到的还是迭代器本身!结果不冲突。
执行结果:
1 <_io.TextIOWrapper name=‘a.txt‘ mode=‘r‘ encoding=‘cp936‘> 2 <_io.TextIOWrapper name=‘a.txt‘ mode=‘r‘ encoding=‘cp936‘>
2、为什么要用迭代器
优点:
(1)迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象(字典,集合,文件)。
(2)迭代器与列表比较,迭代器是惰性计算的,不需要事先准备好集合中的所有元素,仅仅在迭代至某个元素时才计算该元素。适合用于遍历一些大的文件或集合,这样更节省内存。
缺点:
(1)永远无法获取迭代器的长度,使用不如列表利用索引取值灵活。
(2)迭代器中的内容是一次性的,并且用next()取值,只能往前走,不能向后退。
3、检查
1)借助用模块查看查看可迭代对象与迭代器对象
from collections import Iterable,Iterator
isinstance(数据类型,Iterable) 查看是否是可迭代对象
1 from collections import Iterable,Iterator 2 3 s=‘hello‘ 4 l=[1,2,3] 5 t=(1,2,3) 6 d={‘a‘:1} 7 set1={1,2,3,4} 8 f=open(‘a.txt‘) 9 10 # 都是可迭代的,看能不能加上_iter_() 11 s.__iter__() 12 l.__iter__() 13 t.__iter__() 14 d.__iter__() 15 set1.__iter__() 16 f.__iter__() 17 print(isinstance(s,Iterable)) 18 print(isinstance(l,Iterable)) 19 print(isinstance(t,Iterable)) 20 print(isinstance(d,Iterable)) 21 print(isinstance(set1,Iterable)) 22 print(isinstance(f,Iterable))
执行结果:
True True True True True True
isinstance(数据类型,Iterator) 查看是否是迭代器
1 #查看是否是迭代器 简单些就是看能不能调用_next_(),除文件,其他的都不能调用。 2 from collections import Iterable,Iterator 3 4 s=‘hello‘ 5 l=[1,2,3] 6 t=(1,2,3) 7 d={‘a‘:1} 8 set1={1,2,3,4} 9 f=open(‘a.txt‘) 10 11 print(isinstance(s,Iterator)) 12 print(isinstance(l,Iterator)) 13 print(isinstance(t,Iterator)) 14 print(isinstance(d,Iterator)) 15 print(isinstance(set1,Iterator)) 16 print(isinstance(f,Iterator))
执行结果:
1 False 2 False 3 False 4 False 5 False 6 True
4、小结
膜拜如此强大的 for 循环!!!看能否for循环遍历,就能知道是否是可迭代对象……吊炸天!
可以使用for循环进行迭代的对象都是可迭代(Iterable)类型!可以调用next()方法的对象都是迭代器(Iterator)类型!
next(迭代器),就能取迭代器中的值,每次只执行一次,从头开始向前取一个值。取多个就要用到多个next()(超出迭代器中值的个数会报错)或是for循环。
python 给字典内置,就将证明可迭代。只要有_iter_函数,加()就能运行,将字典的keys重新赋值,生成一个返回值,将返回值重新定义就生成了一个迭代器。
三、生成器
1、定义:
关于生成器,可以解释为带有yield的函数就被称为生成器。带有yield的函数不再是一个普通函数,不同于while 死循环、for 循环这种一次性创建完整的庞大的序列打印输出,他在循环的过程中值是不断推算不断生成的,一边循环一边计算的机制。可以理解成:加入了yield函数的循环,所有的执行过程都会在yield函数这里停顿进行判定或是开始,当执行一周再次走到yield函数这里时,会再次停顿进行判定或是开始。
值得注意的是,生成器是可迭代对象,也是迭代器对象。生成器的本质,就是将函数做成了一个迭代器,取名为生成器
2、生成器与return有何区别?
1)return只能执行一次函数就彻底结束了,而yield能返回多次值。
2)生成器就是一个函数,这个函数内包含有yield这个关键字
3)由于生成器是函数类型的迭代器,可以用next() 分步触发函数,也可以for循环。
①next()触发的情况!
1 from collections import Iterator 2 #生成器就是一个函数,这个函数内包含有yield这个关键字 3 def test(): 4 print(‘one‘) 5 yield 1 #return 1 6 print(‘two‘) 7 yield 2 #return 2 8 print(‘three‘) 9 yield 3 #return 3 10 # print(‘four‘) 11 # yield 4 #return 4 12 # print(‘five‘) 13 # yield 5 #return 5 14 15 g=test() #函数运行返回一个值 16 print(g) #打印这个值,显示是生成器类型 17 print(isinstance(g,Iterator)) #查看类型 是否是迭代器 18 # g.__iter__() #可以使用iter()函数 19 # g.__next__() #可以使用next()函数打印值 20 #next()函数会触发生函数的运行,next()一次就触发一次打印一个值。 21 print(next(g))# next()函数触发生函数运行,打印一个值。 22 print(next(g))# next()函数触发生成器运行,打印下个值。 23 print(next(g))# next()函数触发生成器运行,打印下个值。 24 # print(next(g))# next()函数触发生成器运行,打印下个值。 25 # print(next(g))# next()函数触发生成器运行,打印下个值。
执行结果如下:
1 <generator object test at 0x00000000025CB6D0> 2 True 3 one 4 1 5 two 6 2 7 three 8 3
②for循环
1 def test(): 2 print(‘one‘) 3 yield 1 #return 1 4 print(‘two‘) 5 yield 2 #return 2 6 print(‘three‘) 7 yield 3 #return 3 8 g=test() 9 for i in g: 10 print(i)
执行结果与上边一致。
3、yield 函数到底干了什么事情:
1)yield 把函数变成生成器 ---> 迭代器;
2)用return 返回只能返回一次,而yield返回多次;
3)函数在暂停以及继续下一次运行时的状态,是由yield保存。
1 def countdown(n): 2 print(‘start coutdown‘) 3 while n > 0: 4 yield n #1 5 n-=1 6 print(‘done‘) 7 g=countdown(5) 8 print(g) 9 #for 循环方式 10 for i in g: #iter(g) 11 print(i) 12 # while 循环方式 13 # while True: 14 # try: 15 # print(next(g)) 16 # except StopIteration: 17 # break 18 #由于迭代器内的参数是一次性的,输出二选一,while注意要判定异常。 19 #
执行结果:
1 <generator object countdown at 0x000000000260B258> 2 start coutdown 3 5 4 4 5 3 6 2 7 1 8 done
4、yield函数应用
1)实现 linux 中 tail -f /tmp/a.txt 的功能(及监听文件,实时刷新文件的动态,插入内容即刻打印显示,没有就暂停在当前位置)
1 import time 2 def tail(file_path): 3 with open(file_path,‘r‘) as f: #以读的方式打开文件 4 f.seek(0,2) #读取最后一行的文本 5 while True: #循环,一直判断 6 line=f.readline() #一次读一行 7 if not line: #判断,没有值的话,停顿0.3秒,再回去 8 time.sleep(0.3) 9 continue 10 else: 11 # print(line,end=‘‘) #打印一行,此行结尾的换行不打印。不用yield函数的话。 12 yield line # 有文本的话,打印文本,然后停在当前位置 13 g=tail(‘/tmp/a.txt‘) #文件路径 14 print(next(g)) # 打印 实时监听 15 # for line in g: 16 # print(line)
2)实现 linux 中 tail -f /tmp/a.txt |grep ‘error‘的功能(及监听文件,实时刷新文件的动态,实现过滤的功能,及添加进去的内容有‘error‘打印显示,没有不显示)
形象的比喻下:数据流就像水流一样,在一个管道上源源不断的从左往右一直传值。有带着标签的数据流就显示出来,没有就不显示。
1 #/usr/bin/env python 2 import time 3 #定义阶段:定义俩生成器函数 4 def tail(file_path): #管道左边的传值 5 with open(file_path,‘r‘) as f: 6 f.seek(0,2) #一直读取最后一行 7 while True: 8 line=f.readline() 9 if not line: 10 time.sleep(0.3) 11 # print(‘====>‘) 12 continue 13 else: 14 #print(line,end=‘‘) 15 yield line 16 #管道右边的过滤 17 def grep(pattern,lines): # 定义函数(参数)分别为:(过滤的内容,左边写入的一行数据流) 18 for line in lines: #对写入的每行进行遍历循环 19 if pattern in line: #判断是否有匹配上的行 20 yield line # return line 21 22 #调用阶段:得到俩生成器对象 23 g1=tail(‘/tmp/a.txt‘) #为管道左边的传值(要操作的文件)一直监听传入的最后一行的数据 24 g2=grep(‘error‘,g1) #为管道右边的传值(要过滤的数据) 25 26 #next触发执行g2生成器函数 有符合条件的就打印 27 for i in g2: 28 print(i)
5、协程函数
如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
生成器代码执行过程中,碰到yield 程序就会暂停,既然yield 以表达式的形式出现在函数中,就表明将 yield 暂停时从外部所携带来的值 传给等号左边的变量。
整个代码再通过next()函数触发,从yield处开始往下走,代码循环一圈之后,又回到yield这儿停止,以此循环。整个代码通过外部的send()函数给yield传值。
1 #吃包子代码! 2 def eater(name): #定义一个名称函数 name 为人名 3 print(‘%s start to eat food‘ %name) 4 food_list=[] #清单 5 while True: 6 food=yield food_list # 将yeild从外部接收的值传给food 7 print(‘%s get %s ,to start eat‘ %(name,food)) 8 food_list.append(food) #将food添加到清单中 9 print(‘done‘) #结束 10 11 e=eater(‘钢蛋‘) #赋值人名 12 #print(e) 13 print(next(e)) # 开始 触发函数,到yield暂停,将当前值存到food_list中,有一个返回值,打印。 14 print(e.send(‘包子‘)) #为yield传值 拿一个值,将值传给yield当前所对应的变量 food 15 print(e.send(‘韭菜馅包子‘))#为yield传值 16 print(e.send(‘大蒜包子‘))#为yield传值
执行结果:
1 钢蛋 start to eat food 2 [] 3 钢蛋 get 包子 ,to start eat 4 [‘包子‘] 5 钢蛋 get 韭菜馅包子 ,to start eat 6 [‘包子‘, ‘韭菜馅包子‘] 7 钢蛋 get 大蒜包子 ,to start eat 8 [‘包子‘, ‘韭菜馅包子‘, ‘大蒜包子‘]
以上是关于python ---迭代器与生成器的主要内容,如果未能解决你的问题,请参考以下文章