迭代器和生成器
迭代器(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']


浙公网安备 33010602011771号