迭代器
可迭代对象
list、str
、tuple等,可以被遍历的(迭代)
迭代器协议
对象必须提供一个next方法,执行该方法,要么返回迭代中的下一项,要么引起StopIteration
异常,以终止迭代(只能往前进行,不能回退)
现在,我们就可以说,实现了迭代器协议的对象就是可迭代对象。
实现
- 通过在对象内部定义一个
__iter__
方法,有该方法的能够进行迭代
li=[1,2,3]
print(li.__iter__())
li_inter=li.__iter__()
print(li_inter.__next__()) #依次打印下一个
print(li_inter.__next__()) #依次打印下一个
print(next(li_inter)) #依次打印下一个,效果等同上面,继续上面的next
print(li_inter.__next__()) #依次打印下一个,但是超出索引引起异常
#结果#
<list_iterator object at 0x01519E50>
Traceback (most recent call last):
1
2
3
print(li_inter.__next__())
StopIteration
不具备该方法的对象(如int),无法进行迭代
for i in 100:
print(i)
TypeError: 'int' object is not iterable
判断对象能否迭代
from collections.abc import Iterable
print(isinstance([],Iterable))
print(isinstance(1,Iterable))
print(isinstance('',Iterable))
print(isinstance(False,Iterable))
True
False
True
False
测试一自定义类(可以容纳数据)是否可以进行迭代
from collections.abc import Iterable
class DIYClass:
def __init__(self):
self.names=[]
def add(self,name):
self.names.append(name)
my_class=DIYClass()
my_class.add('Tom')
my_class.add('Bob')
print(isinstance(my_class,Iterable)) #该类不能进行迭代
print(isinstance(my_class.names,Iterable)) #列表当然可以进行迭代
False
True
让自定义类可以迭代,为它补充一个__iter__
方法
from collections.abc import Iterable
class DIYClass:
def __init__(self):
self.names=[]
def add(self,name):
self.names.append(name)
def __iter__(self):
pass
my_class=DIYClass()
my_class.add('Tom')
my_class.add('Bob')
print(isinstance(my_class,Iterable))
for i in my_class:
print(i)
True ##虽然具备—__iter__方法,返回True,但是没有具体该方法,所以遍历报错
for i in my_class:
TypeError: iter() returned non-iterator of type 'NoneType'
为__iter__
方法补充具体方式,让其可以直接进行迭代
def __iter__(self): #具体__iter__
return self.names.__iter__()
print(isinstance(my_class,Iterable)) #继续之前的操作
for i in my_class:
print(i,end=' ')
True
Tom Bob
在迭代一个可迭代对象的时候,实际上就是获取该对象提供的一个迭代器。然后通过该迭代器依次获取对象的每一个数据。
for . . . in . . .循环的本质
通过iter()
函数,获取可迭代对象的Iterable
迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值,并且将赋值item,当遇到StopIteration
的异常后,退出。
应用场景
迭代器的核心就是通过next()函数调用返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定规律计算生成。那么也就意味着可以不用依赖一个已有的数据集合Namely,无需将所有的迭代对象数据一次性缓存下来供后续使用。这样,可以节省大量的存储(内存)空间。
斐波那契数列,现在我们希望用for in 的方式来遍历数列中的前n个数
通过迭代来实现,每次迭代都可以通过数学计算生成下一个数,(2个初始值)
class FibIterator:
"""斐波那契数列迭代器"""
def __init__(self,n):
#记录生成的斐波那契数列的个数
self.n=n
#记录当前记录的索引
self.current_index=0
#记录2个初始值
self.num1,self.num2=0,1
def __next__(self):
"""调用next()函数获取下一个数"""
if self.current_index<self.n:
num=self.num1
self.num1,self.num2=self.num2,self.num1+self.num2
self.current_index+=1
return num
else:
raise StopIteration
def __iter__(self):
return self
fib=FibIterator(10)
for i in fib:
print(i,end=' ')
0 1 1 2 3 5 8 13 21 34
自定义一个迭代器
class Test:
def __init__(self,data=1):
self.data=data
def __iter__(self):
return self
def __next__(self):
if self.data>5:
raise StopIteration
else:
self.data+=1
return self.data
for i in Test(): ##效果类似于range
print(i,end=' ')
2 3 4 5 6
注:return self 返回的是类的实例
生成器
解释
生成器就是利用迭代器,在每次迭代获取数据时(通过next()方法)按照特点的规律进行生成
但是在实现一个迭代器时,关于之前迭代的状态需要我们自己记录,这样才能根据当前的状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,可以采用更简便的语法。即生成器。
生成器是一种特殊的迭代器,他比迭代器更优雅。
生成器的创建
列表 与生成器 [ ]---->( )
li=[x**2 for x in range(6)]
gen=(x**2 for x in range(6))
print(li)
print(gen)
while True: #获取生成器元素
try :
print(next(gen),end=' ')
except StopIteration: #捕捉异常读完元素
print('\nIt\'over.')
break
[0, 1, 4, 9, 16, 25]
<generator object <genexpr> at 0x01A45EF0> #表示这是一个生成器
0 1 4 9 16 25
It'over.
遍历生成器
gen=(x**2 for x in range(6))
for i in gen:
print(i,end=' ')
0 1 4 9 16 25
生成器函数
在函数中如果出现了yield关键字,那么该函数就不再是一个普通函数而是一个生成器函数。
def foo():
yield 1
yield 2
f=foo()
print(next(f)) #程序会停留在对于顺序的yield后的语句
print(next(f)) #程序会停留在对于顺序的yield后的语句
print(next(f)) #超出范围的next()报错StopIteration
Traceback (most recent call last):
1
2
print(next(f))
StopIteration
next()与yield会进行匹配,如果next()超出范围,会抛出StopIteration
异常,终止迭代,如果中途遇到return
不会继续return后面的yield,直接抛出StopIteration
异常,如果return 后面有返回值,那么抛出的异常就是该返回值内容,着表示函数的返回值
def show():
yield 1
yield 2
return 'over'
yield 3
s=show()
print(next(s))
print(next(s))
print(next(s))
Traceback (most recent call last):
1
2
print(next(s))
StopIteration: over
生成器的其他方法
close
将生成器直接到抛出异常的位置
def gen():
yield 1
yield 2
yield 3
yield 4
g = gen()
print(next(g))
print(next(g))
g.close()
print(next(g)) # 回溯
Traceback (most recent call last):
1
2
print(next(g))
StopIteration#close提前到抛出异常的位置,再调用就报错
send
-
send()的作用就是使receive赋值为其所传送的值,然后让生成器执行到下一个yield
-
如果生成器未启动,则必须在使用send()前启动生成器,而启动的方法可以是gen.next(),也可以是gen.send(None)执行到第一个yield处。之后就可以使用send()不断的传入值了
-
如果已启动,则send(para)的作用就是给x付志伟发送的值(send的参数),然后让生成器执行到下一个yield
-
obj.send(None)等价于next(obj)
def gen(): value = 0 while True: receive = yield value if receive == "e": break value = "got:%s" % receive g = gen() print(g.send(None)) print(g.send("aaa")) print(g.send(123)) print(g.send("e")) 0 #打开生成器 Traceback (most recent call last): got:aaa #将字符串传给receive并以value的一定格式返回 got:123 #将数字传给receive并以value的一定格式返回 print(g.send("e")) #输入关键字,直接抛出异常终止 StopIteration
总结
生成器存在边循环一边计算的机制,生成器函数为包含yield关键字的函数,它将函数又变成一个迭代器
对于生成器函数补充如下示例
def gen():
i=0
while i<5:
print('in here 1.0')
receive = yield i
print('in here 2.0')
print(receive,i)
i+=1
g = gen()
print(g.send(None)) #等价于next(g)
print('~~~~~~~~~~~~~~~')
print(g.send(None)) #等价于next(g)
print('~~~~~~~~~~~~~~~')
print(g.send(123)) #给receive传入以123
print('~~~~~~~~~~~~~~~')
print(g.send(None)) #等价于next(g)
#结果
in here 1.0 #开启生成器
0 #在yield处停止,没有打印第二句话,也没有打印i和receive,将i=0返回
~~~~~~~~~~~~~~~
in here 2.0 #接到send指令,从上次的断点继续,打印第二句话
None 0 #没有接收参数,为None;i没有自增i=0
in here 1.0 #i完成自增i=1,在yield处停止,没有打印第二句话
1 #将i=1返回
~~~~~~~~~~~~~~~
in here 2.0 #接到send指令,从上次的断点继续,打印第二句话
123 1 #接收到send的参数123赋值给receive,i=1
in here 1.0 #i完成自增i=2,在yield处停止,没有打印第二句话
2 #将i=2返回
~~~~~~~~~~~~~~~
in here 2.0
None 2
in here 1.0
3
由上实例可以看出首先生成器函数与一般不同,但是yield与函数的return相似,每次在return(yield)终止,生成器
开启后,每次迭代停于yield处,下次迭代时,代码从yield的下一条语句开始执行。obj.send(None)等价于next(obj);开启时必须用该方法[next(obj)]开启