详解python可迭代对象、迭代器和生成器
可迭代对象
什么是可迭代对象?顾名思义就是可以迭代的一个对象,再通俗点就是可以被for循环遍历的对象,如常用的list、str等数据类型。我们可以使用isinstance来判断这个数据是否是可迭代对象,在此要先从Iterable包中导入模块collections。
from collections import Iterable
a = 1 #int
b= [1,2,3] #list
c= "abc" #str
d = {1,2,3} #set
e = (1,2,3) #tuple
f = {1:'a',2:'b'}#dict
g = open('test.py')
print (isinstance(a,Iterable))
print (isinstance(b,Iterable))
print (isinstance(c,Iterable))
print (isinstance(d,Iterable))
print (isinstance(e,Iterable))
print (isinstance(f,Iterable))
print (isinstance(g,Iterable))
False
True
True
True
True
True
True
可以看出除了像int、float、complex这种基本类型外,其他的数据类型都是属于可迭代对象,包括文件对象。他们之所以被python内部认为是一种可迭代对象,是因为他们都具有__iter__方法,用hasattr可以看出来。
a = 1 #int
b= [1,2,3] #list
c= "abc" #str
d = {1,2,3} #set
e = (1,2,3) #tuple
f = {1:'a',2:'b'}#dict
g = open('test.py')
print (hasattr(a,'__iter__'))
print (hasattr(b,'__iter__'))
print (hasattr(c,'__iter__'))
print (hasattr(d,'__iter__'))
print (hasattr(e,'__iter__'))
print (hasattr(f,'__iter__'))
print (hasattr(g,'__iter__'))
False
True
True
True
True
True
True
靠__iter__这个方法我们就可以自己创造一个可迭代对象,如下所示:
from collections import Iterable
class A():
def __init__(self):
pass
def __iter__(self):
pass
class B():
def __init__(self):
pass
a = A()
b = B()
print (isinstance(a,Iterable))
print (isinstance(b,Iterable))
True
False
上面代码可以看出来,在一个类中加入了__iter__这个方法,实例化的对象就会被python内部认为是一个可迭代的对象。
迭代器
上面介绍了什么是可迭代对象。那么什么是迭代器呢?迭代器跟可迭代对象又是什么关系呢?那么先解释第一问,迭代器对象是要求支持迭代器协议的对象,在Python中,支持迭代器协议就是实现对象的__iter__()和next()方法。其中__iter__()方法返回迭代器对象本身;next()方法返回容器的下一个元素,在结尾时引发StopIteration异常。事实上,我们可以用上述的方法来检测一下那些数据类型是不是属于迭代器。
from collections import Iterator
a = 1 #int
b= [1,2,3] #list
c= "abc" #str
d = {1,2,3} #set
e = (1,2,3) #tuple
f = {1:'a',2:'b'}#dict
g = open('test.py')
print (isinstance(a,Iterator))
print (isinstance(b,Iterator))
print (isinstance(c,Iterator))
print (isinstance(d,Iterator))
print (isinstance(e,Iterator))
print (isinstance(f,Iterator))
print (isinstance(g,Iterator))
False
False
False
False
False
False
True
结果显示除了文件对象,其他的数据类型都不是迭代器,上面说过,迭代器必须同时有__iter__和__next__方法,我们可以看看文件对象和列表,比较他们是否有__next__方法,依次验证。
print (hasattr(b,'__next__'))#list
print (hasattr(g,'__next__'))#文件对象
False
True
再讲一下可迭代对象和迭代器的区别,我们都知道在学习python时流行这么一句话“一切皆对象”,那么显然易见可迭代对象就包括了迭代器,之所以把把文件对象又叫做迭代器,是因为它多了一种__next__方法而已。
知道了上述所讲的内容后,我们就可以试着自己写一个迭代器,无非就是加入__iter__和__next__方法。
from collections import Iterator
class A():
def __init__(self,i):
self.n = 0
self.i = i
def __iter__(self):
return self
def __next__(self):
if self.i > self.n:
var = self.i
self.i -= 1
return var
else:
raise StopIteration()
a = A(5)
print (isinstance(a,Iterator))
for i in a:
print (i)
#True
#5
#4
#3
#2
#1
创建一个迭代器还可以使用python内置的函数iter和next,如下:
from collections import Iterator
a = [1,2,3]
b = iter(a)
print (next(b))
print (isinstance(b,Iterator))
1
True
上述代码用iter将一个可迭代对象变成了一个迭代器,然后就可以使用迭代器的专用方法next来一个一个地取出数据。
for循环内部机制
a = [1,2,3]
for i in a:
print(i)
#1
#2
#3
为什么一个列表放在for循环里就可以很快地取出内部所有元素呢?正常来说,一个可迭代对象是不能直接从其中取出元素的,但放在for循环里就可以,这是因为在循环时,它会先自动调用__iter__方法将列表变成一个迭代器,然后这个迭代器再调用其__next__()方法。
生成器
首先我们需要知道的是生成器本质就是一个迭代器,它同样拥有__iter__和__next__两个方法。与迭代器不同的是,他可以产生延迟操作,既不会立即出现结果,而是在你需要的时候才产生。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。
def A(n):
print ("----生成器开始-----")
i = 1
while n != 0:
print("开始循环第%d次"%i)
yield n
n -= 1
print ("第%d循环结束"%i)
i += 1
print ("----生成器结束----")
a = A(3)
print (a.__next__())
----生成器开始-----
开始循环第1次
5
可以看出,生成器函数直接调用并不会马上执行,只有用next方法才能开始执行,next一次,他会直到碰到yield就会马上结束,当再次写一个next方法时(如下所示),它会在yield后继续执行下去,直到碰到下一个yield才会停止。如果没有找到yield,就会报出异常。
----生成器开始-----
开始循环第1次
5
第1循环结束
开始循环第2次
4
上述代码如果next方法执行四次,就会报出StopIteration错误。如下所示:
Traceback (most recent call last):
----生成器开始-----
开始循环第1次
File "D:/untitled/test.py", line 20, in <module>
3
第1循环结束
开始循环第2次
2
第2循环结束
开始循环第3次
1
print (a.__next__())
第3循环结束
----生成器结束----
StopIteration
生成器中还有两个很重要的方法:send()和close()。
send(value):next()方法可以恢复生成器状态并继续执行,其实send()是除next()外另一个恢复生成器的方法。与next不同的是,它需要传入一个参数,一般传入None,即send(None)与next()是等效的。
close():这个方法用于关闭生成器,对关闭的生成器后再次调用next或send将抛出StopIteration异常。