迭代器
按照GOF(四人帮GANG OF FOUR)的定义,迭代器提供一种方法访问一个容器对象中的各个元素,而又不需要暴露该对象的内部细节。
在Python中,迭代器就是一个定义了__iter__()和__next__()方法的类的实例对象。__iter__()返回一个迭代器,__next__()返回序列的下一个值。推导式和生成器仅仅是迭代器的简单形式。
>>>it1 =iter([1,2,3])#产生一个列表型('list_iterator')的迭代器实例
>>>type(it1)#来看看it1的类型
#列表迭代器,是一个类实例
#list_iterator的一个实例对象
>>>help(it1)
Help on list_iterator object:
classlist_iterator(object)#list_iterator是一个类
| Methods defined here:
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
|__iter__(self, /)
| Implement iter(self).
|
| __length_hint__(...)
| Private method returning an estimate of len(list(it)).
|
|__next__(self, /)
| Implement next(self).
|
| __reduce__(...)
| Return state information for pickling.
|
| __setstate__(...)
| Set state information for unpickling.
例2,实现一个斐波那契迭代器Fibonacci Iterator
>>>classFib:#(1)
def__init__(self,max):#(2)
self.a = 0
self.b = 1
self.max = max
def__iter__(self):#(3)
returnself
def__next__(self):#(4)
fib = self.a
iffib >self.max:
raiseStopIteration #(5)
self.a,self.b = self.b,self.a + self.b
returnfib#(6)
>>>foriinFib(100):#(7)
print(i,end =' ')
0 1 1 2 3 5 8 13 21 34 55 89
(1),从头开始创建一个迭代器,Fib必须是个类,而不是个函数
(2),调用Fib(max)实际上是创建一个类Fib的实例,并且使用max来调用 __init__()方法。__init__()方法将max值存为一个实例变量,以便稍后引用。
(3), 当调用iter(fib)时,实际上是调用的__iter__()。(for循环可以自动调用它, 当然也可以手动调用它)。__iter__()可以返回实现__next__()方法所需要的任何对象,大多数情况下,它返回的是自身,因为这个类实现的是自己的__next__()方法。
(4),当将next()方法作用于迭代器,实际上调用的是__next__()方法。
(5), 当__next__()方法抛出一个StopIteration异常,这表明迭代已经耗尽,不象其它异常,这里并没发生错误,只意谓着迭代器再也不能产生新值了。如果调用者是一个foo循环,它会留意StopIteration异常,并优雅地退出循环 (换句话说,它把异常吞掉了),这点魔法是把迭代器用于for循环的关键之处。
(6), __next__()方法简单地用return语句返回,就产生了下一个值。此处不能用yield(这是专用于生成器的语法糖)。
(7), Fib(100)返回一个Fib类的实例,暂且称作fib_inst。
for循环调用iter(fib_inst),返回一个迭代器对象,称作fib_iter。这里,fib_iter== fib_inst,因为__iter__()方法返回自身。但for循环并不知道或者说并不关心这点。
为了遍历迭代器,for循环调用next(fib_iter),也就是调用对象fib_iter的__next__()方法,计算斐波那契数的下一个值并返回,for循环取出此值并赋给i,然后在循环体里对i执行打印操作。
for循环如何知道何时退出?当__next__()方法抛出异常StopIteration exception
的时候,for循环吞掉了异常并优雅地退出了,(但其它类型的异常则正常抛出)。当然你在__next__()方法会看到异常StopIteration exception。
for循环解析
foriinobject:
do_work(i)
#等同于
object_iter = iter(object)
whiletrue:
try:
i =next(object_iter)
do_work(i)
exceptStopIteration:
break
注:__init__,__iter__,__next__,前后都带双下划线的方法有些特殊,它们不能被直接调用。 iter(f)实际调用f.__iter__(),next(f)实际调用f.__next__().
A Plural Rule Iterator
classLazyRules:
rules_filename ='plural6-rules.txt'
def__init__(self):
self.pattern_file =open(self.rules_filename, encoding ='utf-8')
self.cache = []
def__iter__(self):
self.cache_index = 0
returnself
def__next__(self):
self.cache_index += 1
iflen(self.cache) >= self.cache_index:
return self.cache[self.cache_index - 1]
ifself.pattern_file.closed:
raiseStopIteration
line = self.pattern_file.readline()
if notline:
self.pattern_file.close()
raiseStopIteration
pattern, search, replace = line.split(None, 3)
funcs = build_match_and_apply_functions(
pattern, search, replace)
self.cache.append(funcs)
returnfuncs
rules =LazyRules()
(将以上代码存为文件plural6.py,然后再另存为plural6-rules.txt,后面有用)
这个类实现了 __iter__()和 __next__(),所以它可用作一个迭代器。在入口处,实例化这个类并赋给rules。
下面分段讲解:
classLazyRules:
rules_filename ='plural6-rules.txt'
def__init__(self):
self.pattern_file =open(self.rules_filename, encoding ='utf-8')#(1)
self.cache = []#(2)
(1), 当你实例化类LazyRules时,打开样板文件,但是并没有读取它。
(2), 用空列表初始化cache,后面在__next__()中,读取样板文件的行时使用它。
继续之前,先来看看rules_filename,它并没有在方法__iter__()中定义,事实上,它在任何方法中都没被定义,它是一个类级别的定义,是一个类变量,尽管你也可以象实例变量一样访问它(self.rules_filename),它被类LazyRules的所有实例共享。
>>>import plural6
>>>r1 = plural6.LazyRules()
>>>r2 = plural6.LazyRules()
>>>r1.rules_filename#(1)
'plural6-rules.txt'
>>>r2.rules_filename
'plural6-rules.txt'
>>>r2.rules_filename ='r2-override.txt'#(2)
>>>r2.rules_filename
'r2-override.txt'
>>>r1.rules_filename
'plural6-rules.txt'
>>>r2.__class__.rules_filename#(3)
'plural6-rules.txt'
>>>r2.__class__.rules_filename ='papayawhip.txt'#(4)
>>>r1.rules_filename
'papayawhip.txt'
>>>r2.rules_filename#(5)
'r2-override.txt'
(1), 每一个类的实例继承了属性rules_filename及其值。
(2), 改变一个实例的属性值,并不影响另一个实例的属性。
(3), 现在改变类的属性。你可以使用特殊的__class__属性访问类本身属性(而不是个体实例属性)。
(4), 改变了类属性,所有继承自它的实例(目前只有r1了)也受影响。
(5), 那些被重新值覆盖的实例(r2)的属性不受影响。
关于迭代,可迭代对象比较好理解,迭代器就难以理解。好多时候有点只可意会,不能言传了。
领取专属 10元无门槛券
私享最新 技术干货