迭代器和生成器
迭代器(iterator)
迭代器协议:必须拥有__iter__方法和__next__方法。
字符串,列表,元组,字典,集合都可以被for 循环,说明他们都是可迭代的。
from collections import Iterable l=[1,2,3,4,5] t=(3,4,5,6,6) d={'a':1,'b':2} s={1,2,3,4,5} print(isinstance(l,Iterable)) print(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(s,Iterable)) # True # True # True # True
结合使用for循环时的现象,可以从字面上理解,迭代就是可以将某个数据集内的数据'一个挨着一个的取出来'。
可迭代协议:内部含有__iter__方法的数据类型就是可迭代的
验证可迭代协议:
print(dir([1,2])) print(dir((2,3))) print(dir({'a':1,'b':2})) print(dir({1,2,3,4})) # ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] # ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] # ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] # ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
由上可以看出,可以被for循环的都是可迭代的,要想可迭代,内部必须要有一个__iter__方法。
执行以下__iter__方法:
print([1,2].__iter__()) # <list_iterator object at 0x000001A68B0210B8>
得到了一个list_iterator,此时我们又得到了一个新的名词——iterator。
同样的,可以对字典、集合、元祖进行__iter__操作
print((1,2,3).__iter__()) print({'a':1,'b':2}.__iter__()) print({1,2,3}.__iter__()) # <tuple_iterator object at 0x0000021688F61080> # <dict_keyiterator object at 0x0000021688E8A4A8> # <set_iterator object at 0x0000021688F60630>
同样的得到了tuple_iterator、dict_keyiterator、set_iterator,这就说明了它们都是可迭代的。
可以通过集合求差集来看可迭代对象与迭代器质检的差别。
print(set(dir([1,2].__iter__()))-set(dir([1,2]))) # {'__next__', '__length_hint__', '__setstate__'} #可以看出可迭代对象比迭代器多了一个__next__功能
对可迭代对象进行操作:
iter_1=[1,2,3,4,5].__iter__() print(iter_1.__length_hint__()) # 从索引为2的元素开始迭代 print('*',iter_1.__setstate__(2)) #一个一个的取值 print('**',iter_1.__next__()) print('***',iter_1.__next__()) # 5 # * None # ** 3 # *** 4
可以看到其中的__next__方法可以实现一个一个的取值。在for循环的内部就是调用了__next__方法才能取到一个一个的值。
写一个使用迭代器的__next__方法来遍历的程序,不需要for循环:
l=[1,2,3,4] l_iter=l.__iter__() item=l_iter.__next__() print(item) item=l_iter.__next__() print(item) item=l_iter.__next__() print(item) item=l_iter.__next__() print(item) item=l_iter.__next__() print(item) # 1 # 2 # 3 # 4 # File "G:/群下载/2.闭包的概念.py", line 206, in <module> # item=l_iter.__next__() # StopIteration
上述方法确实将l中的每个元素都遍历且取出来了,但是当l中的元素个数不清楚 的时候我们会写很多行重复的代码,且当超出范围的时候还会像上面一样报错。
这个时候,我们需要使用异常处理机制来将这个异常来处理掉。用while循环来模拟for循环的原理:———for循环时依赖迭代器,且也可以仿照写一个。
for 循环就是想让我们更简单的使用迭代器
用迭代器取值就不需要关心索引或者key的问题了。
l=[1,2,3,4] l_iter=l.__iter__() while True: try: item=l_iter.__next__() print(item) except StopIteration: break # 1 # 2 # 3 # 4
range()方法:
# 查看__iter__方法中range()使用dir()方法之后是否还存在 print('__iter__'in dir(range(12))) #查看__next__方法中range()使用dir()方法之后是否还存在 print('__next__' in dir(range(12))) from collections import Iterable print(isinstance(range(100),Iterable)) # 验证range执行之后得到的结果是不是一个迭代器 # True # False # True
可迭代的必须要含有__iter__方法 # 可迭代协议
迭代器比可迭代的多一个__next__方法
迭代器:包含__next__,__iter__方法的就是迭代器。 # 迭代器协议
包含__next__方法的可迭代对象就是迭代器
迭代器是可迭代的一部分
获得迭代器:可迭代的调用__iter__()
使用迭代器:__next__()
如何判断一个变量是不是迭代器或者可迭代的
方法一:
print('__iter__' in dir([1,2,3,4])) print('__next__' in dir([1,2,3,4])) # True # False # 由此可以看出[1,2,3,4]有__iter__用法,没有__next__用法,说明该列表是一个可迭代对象,
#但不是一个迭代器。
方法二:
from collections import Iterable print(isinstance(([1,2,3,4]),Iterable)) # True str_iter='abc'.__iter__() print(isinstance(str_iter,Iterable)) # True print(isinstance('abc',Iterable)) # True
只是记录当前这个元素和下一个元素
range_iter=range(10).__iter__() print(range_iter.__next__()) print(range_iter.__next__()) # 0 # 1
总结迭代器的特点:1、惰性运算;
2、从前到后依次的取值,但是过程是不可逆的,不可重复的;
3、节省内存
生成器:
用生成器做监听文件的程序
def tail(): f = open('文件', 'r', encoding='utf-8') # 打开文件 f.seek(0, 2) # 将光标移到文件内容的最后 while True: line = f.readline() # 读出文件中光标之后的内容 if line: # 如果有则输出新加的内容 yield line g = tail() for i in g: print(i.strip())
send用法:
1、从哪一个yield开始接着执行,就把一个值传给了那个yield
2、send不能用在第一个触发生成器
3、生成器函数中有多少个yield就必须有多少个next+send
def func(): print('*'*10) a = yield 5 print(a) b = yield 10 g = func() num = g.__next__() print(num) num2 = g.send('alix') print(num2) # ********** # 5 # alix # 10
利用send计算滚动平均值:
def avarge(): total=0.0 count=0 avarge=None while True: trem=yield avarge count+=1 total+=trem avarge=total/count g=avarge() avg_num=g.__next__() avg_num=g.send(10) print(avg_num) avg_num=g.send(30) print(avg_num) avg_num=g.send(60) print(avg_num) # 10.0 # 20.0 # 33.333333333333336
将上面的滚动平均值改成装饰器的形式:
def init(func): # 生成器的预激装饰器 def inner(*args,**kwargs): g=func(*args,**kwargs) # func=avarge g.__next__() return g return inner @init def avarge(): total=0.0 count=0 avarge=None while True: trem = yield avarge count += 1 total += trem avarge = total/count g = avarge() avg_num=g.send(10) print(avg_num) avg_num=g.send(30) print(avg_num) avg_num=g.send(60) print(avg_num) # 10.0 # 20.0 # 33.333333333333336
# 将a='AB',b='CD'输出,输出成'A','B','C','D' def func(): a = 'AB' b = 'CD' yield from a yield from b g=func() g_s=g.__next__() print(g_s) g_s=g.__next__() print(g_s) g_s=g.__next__() print(g_s) g_s=g.__next__() print(g_s) # A # B # C # D
或将上面的代码简化
# 将a='AB',b='CD'输出,输出成'A','B','C','D' def func(): a = 'AB' b = 'CD' yield from a yield from b g=func() for i in g: print(i) # A # B # C # D
触发执行的方式:
next和send是执行几次拿几个数据,在取值的过程中不知道到底有多少个,可能会超出范围,当超出范围的时候会报错。
其中send(None)==__next__(),send中next的基础上传一个值到生成器函数内部;send操作不能用在生成器使用的第一次。
for循环每次取一个值,取完为止,不会报错
def cloth(): for i in range(100): yield '衣服%s'%i g = cloth() for c in g: print(c) if c.endswith('20'): # 打印到衣服20 break print(g.__next__()) # 再次触发生成器,输出衣服21到衣服99 print('*'*20) for c in g: print(c)
列表推导式:
求出l=[1,2,3,4,5,6,7]中每个元素的平方并存入新的列表中
#方法一: l=[1,2,3,4,5,6,7] li=[] for i in l: li.append(i*i) l=li print(l) # [1, 4, 9, 16, 25, 36, 49] #方法二: l=[1,2,3,4,5,6,7] x=[i*i for i in l] print(x) # [1, 4, 9, 16, 25, 36, 49]
生成器表达式:
l=[1,2,3,4,5,6,7] g=(i*i for i in l) for i in g: print(i) # 1 # 4 # 9 # 16 # 25 # 36 # 49
使用生成器的优点:
1、延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这在处理较多数据的时候可以节省内存空间。
2、提高代码可读性
字典的推导式:
找出names中含有2个e的名字:
names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'},
{'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}]
names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'}, {'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}] ret=[name for name_list in names for name in name_list if name.count('e')>=2] # 相当于两个for循环的嵌套,之后再加上一个if判断 print(ret) # ['Steven', 'Jefferson', 'Wesley', 'Jennifer']