Python迭代器详解
# collections是一个包含了许多类型的module
from collections import iterator,iterable,generator
**生成器generator
generator对象是一种特殊的iterator函数,它会在执行过程中保存执行的上下文环境,并在下次执行中从yield语句后继续计算。
generator不需要抛出stopiteration异常(你可以看做yield已经在内部实现了stopiteration跳出循环),函数并没有将序列项一次生成,所以generator在实现上可以有无穷个元素,而不需要无穷的存储空间,这在内存优化方面很有用处。
使用isinstance(实体名,generator)可判断是否为生成器。
**可迭代对象iterable
可迭代的对象的意思是就是说这个实体是可迭代的,例如字符、列表、元组、字典、迭代器等等,可以用for ... in进行循环。
可迭代对象仅含有__iter__方法,你可以通过封装next方法(python3中为__next__)来将其做成一个迭代器。以上边的生成器为例,你可以通过yield关键字来做一个迭代器,只不过名字被叫做generator,yield实质上就是为对象添加了指示下一个项目的next()/__next__()方法。
使用isinstance(实体名,iterable)可判断是否为可迭代对象。
**迭代器iterator
迭代器就是实现了迭代方式的容器,iterable对象一般只能按默认的方式进行迭代,你可以通过为其添加__next__()/next()方法来定制不同的迭代方式,这样通过next方法封装起来的迭代对象生成器就被称作迭代器。与iterable相比iterator多了一个next()方法,next()方法用于定制for循环时具体的返回值及返回顺序等。
使用isinstance(实体名,iterator)可判断是否为迭代器。
**__iter__()和iter()
python有一个built-in函数iter()用来从序列对象,如string, list,tuple中生成迭代器。
__iter__()方法返回一个迭代对象,然后python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到stopiteration错误时退出循环。因此如果对象不含__next__方法,但是__iter__只返回self的话就会报“typeerror: iter() returned non-iterator of type [类名]”,针对这种错误要么加一个__next__()方法,要么__iter__()返回一个包含__next__()方法的迭代器对象。
二、示例
示例一:(python3环境)
# 先看一个iterale对象
in [91]: from collections import iterator,iterable,generator
in [92]: a=['shanxi','hunan','hubei','*','jiangsu','xizang','henan','hebei']
in [93]: isinstance(a,iterator),isinstance(a,iterable),isinstance(a,generator)
out[93]: (false, true, false)
# 可以看到这是一个可迭代对象但并不是迭代器,我们把它搞成一个迭代器试试看:
# 方法一:
in [102]: def generator_list(a):
...: for e in a:
...: yield 'province:\t'+e
in [105]: for province in generator_list(a):
...: print(province)
province: shanxi
# ...其他输出省略
in [122]: isinstance(generator_list(a),generator),isinstance(generator_list(a),iterable),isinstance(generator_list(a),iterator)
out[122]: (true, true, true)
# 方法2:
class iterator_list(object):
def __init__(self,a):
self.a=a
self.len=len(self.a)
self.cur_pos=-1
def __iter__(self):
return self
def __next__(self): # python3中只能使用__next__()而python2中只能命名为next()
self.cur_pos +=1
if self.cur_pos<self.len:
return self.a[self.cur_pos]
else:
raise stopiteration() # 表示至此停止迭代
in [144]: for province in iterator_list(a):
...: print(province)
in [147]: isinstance(iterator_list(a),generator),isinstance(iterator_list(a),iterable),isinstance(iterator_list(a),iterator)
out[147]: (false, true, true)
# iterator当然是iterable,因为其本身含有__iter__方法。
问题:既然可迭代对象也可以使用for循环遍历,为何还要使用迭代器呢?
一般情况下不需要将可迭代对象封装为迭代器。但是想象一种需要重复迭代的场景,在一个class中我们需要对输入数组进行正序、反序、正序step=1、正序step=2等等等等的多种重复遍历,那么我们完全可以针对每一种遍历方式写一个迭代容器,这样就不用每次需要遍历时都费劲心思的写一堆对应的for循环代码,只要调用相应名称的迭代器就能做到,针对每一种迭代器我们还可以加上类型判断及相应的处理,这使得我们可以不必关注底层的迭代代码实现。
从这种角度来看,你可以将迭代器看做可迭代对象的函数化。
示例二:(python2环境)
#-*- coding: utf-8 -*-
# 简便起见这里只写python2的代码,想要在python3中运行将print修改下再把next()改名为__next__即可。
list=['a','b','c','d','e','f','g','h','i','j']
class iter_standard(object):
def __init__(self,list):
self.list=list
self.len = len(self.list)
self.cur_pos = -1
def __iter__(self):
return self
def next(self):
self.cur_pos += 1
if self.cur_pos<self.len:
return self.list[self.cur_pos]
else:
raise stopiteration()
class iter_reverse(object):
def __init__(self,list):
self.list=list
self.len = len(self.list)
self.cur_pos = self.len
def __iter__(self):
return self
def next(self):
self.cur_pos -= 1
if self.cur_pos>=0:
return self.list[self.cur_pos]
else:
raise stopiteration()
for e in iter_standard(list):
print e
for e in iter_reverse(list):
print e
可以看到我们只要调用相应名字的迭代器对象就可以直接进行for循环了,这种写法相比起每次都需要在for循环中重复一遍算法逻辑要简单,除此之外你还可以为不同输入类型定制相同的迭代方式,这样就无需关注内部实现了。这就是迭代器的作用,为不同类型的输入封装相同的迭代功能,从而实现代码简化。python中有一个非常有用的itertools module,提供了大量不同的迭代器,只要直接调用你就可以实现对序列的各种操作,你可以通过这个库加深对于迭代器的理解。
示例三:(python2环境)
# 在github项目pymysqlreplication里发现,作者并未为class binlogstreamreader专门写__next__方法,而是在__iter__里直接返回一个迭代器对象,这个迭代器对象是使用iter()方法调用self.fetchone生成的,代码如下:
# module binlogstream部分代码如下:
"""
class binlogstreamreader(object):
......
def fetchone(self):
while true:
... # 各种参数赋值和终止条件定义
binlog_event = ...
if binlog_event.event_type ...
...
return binlog_event.event
......
def __iter__(self):
return iter(self.fetchone, none)
"""
# fetchone是class binlogstreamreader的一个方法,使用while循环根据不同的条件进行判断,返回event序列,这个可迭代的序列使用iter()处理后就是一个迭代器了,因此直接在__iter__中返回后就取代了__next__的作用。
# 因为不含__next__方法因此使用isinstance()判断binlogstreamreader是否为迭代器时就会出错,虽然结果显示不是迭代器,但其实确实是迭代器...
# 我们改写下示例二中的class iter_standard来验证这个错误:
#-*- coding: utf-8 -*-
from collections import iterator,iterable
list=['a','b','c','d','e','f','g','h','i','j']
class iter_standard(object):
def __init__(self,list):
self.list=list
def __iter__(self):
return iter(self.list)
print isinstance(iter_standard(list),iterable),isinstance(iter_standard(list),iterator)
# 结果如下图,isinstance并不认为iter_standard类是一个迭代器。