迭代器

 

参考资料

1.函数式编程:生成器 http://www.cnblogs.com/huxi/archive/2011/07/14/2106863.html

2.深入理解生成器 http://www.cnblogs.com/jessonluo/p/4732565.html

3.Python 迭代器小结 http://python.jobbole.com/85240/

4.迭代器的原理和使用 https://www.cnblogs.com/zhangqigao/p/5441930.html

5.可迭代对象 vs 迭代器 vs 生成器 http://python.jobbole.com/86258/

6.如何更好地理解Python迭代器和生成器? https://www.zhihu.com/question/20829330

7.理解python的迭代器 http://www.cnblogs.com/jessonluo/p/4726995.html

8.python函数式编程指南3:迭代器  http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html

 

1.什么是迭代

遍历 Traverse: 访问一个集合(广义)的每个元素。访问所有的数据即为遍历,遍历的方法可以用递归或者迭代。
迭代 Iterate: 反复调用同一个过程最终达成目的,这个过程如果是一个函数那就是递归;如果是一个循环体,那就是狭义上的迭代—for循环访问容器中的每一个元素值,直到没有更多元素为止。

遍历是目的,迭代是手段

参加迭代的,一定是一个包含两个以上元素的数据结构;

但并不是包含两个以上元素的数据结构都可以迭代。

2.需求从何而来

(1)为什么数列、元组等集合可以被for循环遍历每一个值?(如下)

x = [1,2,3]
for elem in x:
    do_something_to(elem)

(2)能不能自己随便编写一个什么也能够被遍历?

当然我尝试失败了,我的类myrange并不能遍历,如下:

class MyRange:
...     def __init__(self, num):
...         self.num = num
...
>>> for i in MyRange(10):
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'MyRange' object is not iterable

 

冷静!先研究一下for循环。当我们在进行上面x对象代码的时候:

实际上的调用过程是:

先看一下iter()函数和next()函数的运作原理:

(1) iter()函数

功能:用来生成迭代器

语法:iter(object[,sentinel])

  • object -- 支持迭代的集合对象。
  • sentinel -- 如果传递了第二个参数,则参数 object 必须是一个可调用的对象(如,函数),此时,iter 创建了一个迭代器对象,每次调用这个迭代器对象的__next__()方法时,都会调用 object。

返回值:迭代器对象。

实例:

>>>lst = [1, 2, 3]
>>> for i in iter(lst):
...     print(i)
... 

(2)next函数

功能:返回迭代器的下一个项目

语法:next(iterator[, default])

参数说明:

  • iterator -- 可迭代对象
  • default -- 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常。

实例:

1 # 首先获得Iterator对象:
 2 it = iter([1, 2, 3, 4, 5])
 3 # 循环:
 4 while True:
 5     try:
 6         # 获得下一个值:
 7         x = next(it)
 8         print(x)
 9     except StopIteration:
10         # 遇到StopIteration就退出循环
11         break
12 #输出结果为12345

回到正题,来看for 语句的步骤是:

  1. 先判断对象x是否为可迭代对象,即看这个对象有没有内置_iter_方法:没有的话直接报错,抛出TypeError异常;有的话为可迭代对象。如:
  2. c = 2
    b = iter(c)
    #打印结果为
    #int object is not iterable
    上面的例子中,由于对象c没有内置函数_iter_,所以iter函数是无法调用对象c的,导致报错
  3. 有_iter_方法的对象,for函数则用iter函数调用对象的 _iter_方法,进而调用此对象,格式如:
  4. b = iter(c)
  5. 并将x的返回值保存,即产生一个带有_next()_方法的迭代器,如上例中的b即是一个迭代器。
  6. for再不断地用next()函数调用这个迭代器的__next__方法,每次按序返回迭代器中的一个值,即实现了遍历每一个值。如:
  7. >>>next(b)
  8. 迭代到最后,没有更多元素了,就抛出异常 StopIteration,这个异常 python 自己会处理,不会暴露给开发者。

对于被for的对象而言,只要它具有_iter_方法证明自己是可被迭代的(以上的步骤1),剩下的获取返回值建立迭代器并通过迭代器的_next_函数获取下一个值的工作,就由for来完成(以上的2,3,4)。所以上面对x对象进行for循环的秘密是:

1 fetch = iter(x)  
2 while True:  
3 try:  
4 i = fetch.next()  
5 except StopIteration:  
6 break  
7 do_something_to(elem)  

因此,(1)可迭代对象一定是具有_iter_方法的,可以用对象.iter()的方法查看;

这是一个可迭代对象,因为它具有_iter_方法:

x = [1,2,3]
x.__iter__()

           (2)但是可迭代对象不一定是迭代器,它需要手动或者自动保存自身调用iter函数后的返回值,才能生成一个迭代器:

综上,有些可以对象被for而有些不可以被for,本质上只是因为有些对象具有iter()函数,能够被for语句调用自动产生迭代器,进而调用迭代器内的_next(函数实现遍历。

for循环的秘密:iter()函数+next()函数

问题(2)似乎有了答案:要让一个对象实现遍历,从iter和next入手。

如果一个对象不是可迭代对象而无法遍历,那就帮助它实现iter,再实现next。

PS:一般而言,容器大部分是可以迭代的,并非所有的容器都是可迭代对象:

PPS:容器是用来储存元素的一种数据结构,它支持隶属测试,可以为理解为两个以上元素的集合,容器将所有数据保存在内存中,在Python中典型的容器有:

  • list, deque, …
  • set,frozesets,…
  • dict, defaultdict, OrderedDict, Counter, …
  • tuple, namedtuple, …
  • str

如果一个对象可迭代(iterable),即它有_iter_方法,要么用具有直接调用功能的for实现迭代,要么手动调用它的_next_实现迭代,如问题(1)中的列表。

其中手动实现迭代的方式,叫做自定义迭代器。自定义迭代器一般是写成一个类,在类中加入_iter_函数使其可迭代,同时加入_next_函数使其有算法可计算。

进一步地,将_iter_方法和_next_方法成为迭代协议,将具有_iter_方法和_next_方法的对象成为迭代器(iterator)。

改进问题(2)中我的代码:

class MyRange:
    def __init__(self, num):
        self.i = 0 #头
        self.num = num #尾

    def __iter__(self):#在我的类myrange中加入了迭代的入门条件:iter接受对象并返回一个迭代器
        return self

    def __next__(self):#进一步加入并完善_next_函数,next接受迭代器
        if self.i < self.num:
            i = self.i
            self.i += 1
            return i
        else:
            # 达到某个条件时必须抛出此异常,否则会无止境地迭代下去
            raise StopIteration() 

再次运行:

for i in MyRange(3):
    print(i)
# 输出
 0
 1
 2
#这个类既是可迭代的 (因为具有__iter__()方法),也是它自身的迭代器(因为具有__next__()方法)

上面的方法是让for去调用next函数进而迭代,也可以这样手动调用,效果一样:

c = iter(MyRange(5))#切记不可忘记这一步
print(next(c))
print(next(c))
print(next(c))

谨记,在手动迭代的时候,有iter()和next()两个步骤。其中iter()是来生成迭代器,next()是调用迭代器进行迭代。一个误区是,用for语句的时候,它可以为对象做完这两步,手动的时候要记得自己做完这两步。

再加一个清晰思路的例子(打算我这个例子因为对齐有问题所以无法正常打印,逻辑和写法没问题):

#第一个例子:一个手动实现迭代的例子
a = [1,2,3,4,5]
#下面这几步相当于 for i in a:print i
b = iter(a)
print(next(b))
print(next(b))
print(next(b))
print(next(b))
print(next(b))
#打印结果是 1 2 3 4 5
#第二个例子:写一个类,实现上面同样的功能
class mylist:
    def __init__(self,to_end):#在初始化类的时候,先确定迭代的起点和终点
        self.start = 1
        self.to_end = to_end

    def __iter__(self):#用iter方法将自身可迭代化
        return self

    def __next__(self):#准备写一个算法放进_next_()函数中,说明数据之间的逻辑关系
        if self.start < self.to_end:
            i = self.start
            self.start += 1
            return i
        else:
            raise StopIteration() 
#然后首先调用iter函数,对应类里面的_iter_()方法,为类生成一个迭代器
b = iter(mylist(5))
#再用next(),对应类里面的_next_()方法,按照_next_方法的算法,开始计算
print(next(b))
print(next(b))
print(next(b))
print(next(b))
print(next(b))      

一个重要区别

next()函数与_next_()方法:两者互相对应,next()用来调用_next_(),_next_()在内,用来写明算法,next()在外。要先有_next_(),才能用next()

iter()函数和_iter_()方法:上同

 还有一个重要认识

如上面的例子所示,其实自己写一个迭代器的过程,就是一个自己生成某种数据结构的过程,譬如上面的例子中我生成了一个列表,意义上相当于列表生成式 b = [i for i in 5],列表生成式总结了我的类,把它精简为一个小小的式子,有点像语法糖

当然也可以写其他的数据结构的迭代,意义上也相当于别的数据结构生成式的效果

还有一个不成熟的猜测

其实迭代器就是一个可以实现迭代的对象,通过一定的方式(next和iter)来遍历自己的变量。

比如列表a,在未对它采取任何操作前,它确实是可以迭代的,它的结构决定了它的元素可以被遍历,但它还不是一个迭代器,因为它还没有实现迭代

当调用for语句后,列表实现了迭代,但它仍然不是迭代器,因为for对它的操作,包括生成一个迭代器,并没有保存到列表的语法中。可以说,在for过程中,a确实有一个迭代器,但不像类那样为自身所有,而是平行关系。

真正的迭代器一定是一个类(对象),在类中有自己的next函数。可以这样说,在列表a的类中,没有next函数,所以它不是迭代器——这要怪列表的源代码定义!

更深入理解迭代器

迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。

迭代器不能回退,只能往前进行迭代。这并不是什么很大的缺点,因为人们几乎不需要在迭代途中进行回退操作。

迭代器也不是线程安全的,在多线程环境中对可变集合使用迭代器是一个危险的操作。但如果小心谨慎,或者干脆贯彻函数式思想坚持使用不可变的集合,那这也不是什么大问题。

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值,这是后话)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

迭代器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素。如迭代一个已知元素的列表a=【1,2,3】,此时已知所有元素在开始迭代;迭代器可以先提供算法,再来计算元素的值,如列表生成式 a =(i*i for i in 10)

迭代器更大的功劳是提供了一个统一的访问集合的接口。只要是实现了__iter__()方法的对象,就可以使用迭代器进行访问。任何具有__next__()方法的对象都是迭代器,对迭代器调用next()方法可以获取下一个值。

迭代器本质上是一个产生值的工厂,每次向迭代器请求下一个值,迭代器都会进行计算出相应的值并返回。

迭代器内部状态保存在当前实例对象的prev以及cur属性中,在下一次调用中将使用这两个属性。每次调用next()方法都会执行以下两步操作:

  1. 修改状态,以便下次调用next()方法
  2. 计算当前调用的结果

比喻:从外部来看,迭代器就像政府工作人员一样,没人找他办事的时候(请求值),工作人员就闲着,当有人来找他的时候(请求值),工作人员就会忙一会,把请求的东西找出来交给请求的人。忙完之后,又没事了,继续闲着。

复制迭代器

迭代器是一次性消耗品,使用完了以后就空了,请看。

>>> L=[1,2,3]
>>> I=iter(L)
>>> for i in I:
...     print(i, end='-')
...
1-2-3-
>>>next(I)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
当循环以后就殆尽了,再次使用调用时会引发StopIteration异常。

我们想通过直接赋值的形式把迭代器保存起来,可以下次使用。
但是通过下面的范例可以看出来,根本不管用。

>>> I=iter(L)
>>> J=I
>>> next(I)
1
>>> next(J)
2
>>> next(I)
3
>>> next(J)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

 

那怎么样才能达到我们要的效果呢?
我们需要使用copy包中的deepcopy了,请看下面:

>>> import copy
>>> I=iter(L)
>>> J=copy.deepcopy(I)
>>> next(I)
1
>>> next(I)
2
>>> next(J)
1
 

 


posted @ 2018-01-12 11:54  凯曼  阅读(121)  评论(0)    收藏  举报