迭代器小结

作者: PyChina | 来源:发表于2016-09-09 17:53 被阅读54次

    作者:邵正将 来源:PytLab

    迭代器

    迭代器是在python2.2中被加入的,它为类序列对象提供了一个类序列的接口。有了迭代器可以迭代一个不是序列的对象,因为他表现出了序列的行为。当在python中使用for循环迭代一个对象时,调用者几乎分辨不出他迭代的是一个迭代器对象还是一个序列对象,因为python让他(迭代器)像一个序列那样操作。

    如何迭代

    本质上说迭代器是个对象,但是这个对象有个特殊的方法next()(在python3中使用__next__()代替了next方法)。当使用for循环来遍历整个对象时候,就会自动调用此对象的__next__()方法并获取下一个item。当所有的item全部取出后就会抛出一个StopIteration异常,这并不是错误的发生,而是告诉外部调用者迭代完成了,外部的调用者尝试去捕获这个异常去做进一步的处理。
    不过迭代器是有限制的,例如

    • 不能向后移动
    • 不能回到开始
    • 也无法复制一个迭代器。
      因此要再次进行迭代只能重新生成一个新的迭代器对象。

    获取迭代器

    1. 对于python内置的可迭代(iterable)对象,可以通过内置的iter()函数来获取相应的迭代器对象。

      In [1]: a = [1,2,3,45]
      
      In [2]: type(a)
      Out[2]: list
      
      In [3]: a = iter(a)
      
      In [4]: type(a)
      Out[4]: list_iterator
      

      这样就获取了list相应的迭代器对象。
      我们来看一下该迭代器对象的属性:

      In [5]: dir(a)
      Out[5]:
      ['__class__',
       '__delattr__',
       '__dir__',
       '__doc__',
       '__eq__',
       '__format__',
       '__ge__',
       '__getattribute__',
       '__gt__',
       '__hash__',
       '__init__',
       '__iter__',
       '__le__',
       '__length_hint__',
       '__lt__',
       '__ne__',
       '__new__',
       '__next__',
       '__reduce__',
       '__reduce_ex__',
       '__repr__',
       '__setattr__',
       '__setstate__',
       '__sizeof__',
       '__str__',
       '__subclasshook__']
      
      In [6]:
      

      可见此迭代对象具有两个特殊的成员方法__iter__()__next__(),这两个方法便是支持迭代器协议所需要实现的方法。其中__iter__()方法返回迭代器对象本身,__next__()方法返回容器的下一个元素,直到结尾抛出StopIteration异常。
      我们来测试一下这个list_iterator对象的这两个方法:

      __iter__()返回的对象就是迭代器对象本身。

      In [1]: a = [1,2,3,45]
      
      In [2]: a = iter(a)
      
      In [3]: a.__iter__()
      Out[3]: <list_iterator at 0x3a33f10>
      
      In [4]: a
      Out[4]: <list_iterator at 0x3a33f10>
      
      In [5]: a is a.__iter__()
      Out[5]: True
      
      In [6]:
      

      __next__()方法返回容器中的值直到结尾。

      In [6]: a.__next__()
      Out[6]: 1
      
      In [7]: a.__next__()
      Out[7]: 2
      
      In [8]: a.__next__()
      Out[8]: 3
      
      In [9]: a.__next__()
      Out[9]: 45
      
      In [10]: a.__next__()
      ---------------------------------------------------------------------------
      StopIteration                             Traceback (most recent call last)
      <ipython-input-10-73aa2c76d676> in <module>()
      ----> 1 a.__next__()
      
      StopIteration:
      
      In [11]:
      
    2. 创建迭代器对象
      除了使用iter()函数将内置的序列对象转换成相应的迭代器,我们可以自己实现迭代器协议创建迭代器对象,要实现迭代器协议也就是要在类中实现__iter__()__next__()方法。
      下面我写一个与list_iterator相同行为的迭代器:

      class ListIter(object):
          def __init__(self, data):
              self.__data = data
              self.__count = 0
      
          def __iter__(self):
              return self
      
          def __next__(self):
              if self.__count < len(self.__data):
                  val = self.__data[self.__count]
                  self.__count += 1
                  return val
              else:
                  raise StopIteration
      

      我们就可以使用for循环来遍历这个迭代器了:

      In [16]: a = ListIter([1,2,3,4,5])
      
      In [17]: for i in a:
         ....:     print(i)
         ....:
      1
      2
      3
      4
      5
      
      In [18]:
      

      对于迭代器对象,使用for循环遍历整个数组其实是个语法糖,他的内部实现还是通过调用对象的__next__()方法。
      实际上他内部的工作原理应该是这样的:

      a = ListIter([1, 2, 3, 4, 5])
      
      while True:
          try:
              i = a.__next__()
          except StopIteration:
              break
          // do something in for loop
          print(i)
      

    迭代器支持多次迭代

    正如前面所说的迭代器对象不支持重新迭代,也就是同一个迭代器对象无法多次迭代,如:

    In [19]: a = ListIter([1,2,3,4,5])
    
    In [20]: [i for i in a]
    Out[20]: [1, 2, 3, 4, 5]
    
    In [21]: [i for i in a]
    Out[21]: []
    
    In [22]:
    

    可见,当我再次迭代迭代器a的时候便只返回了空列表,这是因为for循环直接捕获了StopIteration异常。如果要再次迭代生成列表的话只能重新生成一个新的迭代器对象。
    为了能够解决这个问题,可以分别定义一个可迭代对象(iterables)和迭代器对象(iterator).

    插入小插曲;
    {% alert info %}
    对于可迭代对象和迭代器对象,我的理解是:


    <strong>可迭代对象</strong>是实现了<code>iter()</code>方法的对象,<code>iter()</code>可以返回一个迭代器对象。


    <strong>迭代器对象</strong>是实现了<code>next()</code>方法的对象,其中他的<code>iter()</code>返回的是迭代器对象本身。
    {% endalert %}

    我把代码做了修改,如下:

    class ListIterable(object):
        def __init__(self, data):
            self.__data = data
    
        def __iter__(self):
            print("call iterable __iter__().")
            return ListIterator(self.__data)
    
    
    class ListIterator(object):
        def __init__(self, data):
            self.__data = data
            self.__count = 0
    
        def __iter__(self):
            print("call iterator __iter__().")
            return self
    
        def __next__(self):
            print("call iterator __next__().")
            if self.__count < len(self.__data):
                val = self.__data[self.__count]
                self.__count += 1
                return val
            else:
                raise StopIteration
    

    为了知道python何时调用__iter__()方法,我添加了一个printf函数来做标记。

    现在把这两个类导入到当前空间中:

    In [1]: from list_iter import *
    
    In [2]: a = ListIterable([1,2,4,5,6])
    
    In [3]: b = a.__iter__()
    call iterables __iter__().
    
    In [4]: a
    Out[4]: <list_iter.ListIterable at 0x39446d0>
    
    In [5]: b
    Out[5]: <list_iter.ListIterator at 0x39447b0>
    
    In [6]:
    

    可见a是iterable对象(实现了__iter__()),b是iterator对象(实现了__next__())。

    下面看看这样做是不是就可以重复多次迭代了:

    In [6]: [i for i in a]
    call iterable __iter__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    Out[6]: [1, 2, 4, 5, 6]
    
    In [7]: [i for i in a]
    call iterable __iter__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    call iterator __next__().
    Out[7]: [1, 2, 4, 5, 6]
    
    In [8]:
    

    重复迭代是可以了,从输出中我们可以看出一些什么来

    1. 我们在使用迭代工具对iterable对象进行迭代的时候首先调用的是iterable__iter__()方法,返回一个迭代器对象,也就是ListIterator的实例。
    2. 然后再遍历的时候是调用iterator的next方法输出值。
      这样就可以解释了为什么这样处理能够多次迭代了,因为每次使用迭代工具迭代的时候都会调用__iter__()返回一个新的迭代器对象,这样就相当于创建多个迭代器了,自然可以看起来是重复迭代了!

    可变对象和迭代器

    在迭代可变对象时候,一个序列的迭代器只是记录当前到达了序列中的第几个元素,所以如果在迭代过程中改变了序列的元素。更新会理解反应到所迭代的条目上。
    我写了个测试看了下,的确:

    In [13]: c = [1,2,3,4,5]
    
    In [14]: d = iter(c)
    
    In [15]: for i in c:
       ....:     print(i)
       ....:     c.remove(i)
       ....:
    1
    3
    5
    

    可见上面边迭代边删除列表的元素,但是最后却只输出了1, 3, 5,这是为啥?
    既然迭代器只记得是在列表中第几个元素,那么当在第0个元素的时候将会输出1然后删除1,这是列表变成了

    [2, 3, 4, 5]
    

    但是迭代器记得我是在第二个位置上面,就指向了列表中的第二个位置上,也就是3,然后输出3.
    以此类推,最后只能输出1,3,5了。
    如果我猜测的没错的话,剩余的列表应该只剩下2和4了:

    In [17]: c
    Out[17]: [2, 4]
    

    果然!

    以上就是我对python中迭代器方面的一点小总结。

    今年第六届大会PyConChina2016,由PyChina.org发起,CPyUG/TopGeek 等社区协办,将在2016年9月10日(上海)9月23日(深圳)10月15日(北京)地举办的针对Python开发者所举办的最盛大和权威的Python相关技术会议,由PyChina社区主办,致力于推动各类Python相关的技术在互联网、企业应用等领域的研发和应用。

    您可以点击此处
    了解更多详情,或者扫描下图二维码:

    相关文章

      网友评论

        本文标题:迭代器小结

        本文链接:https://www.haomeiwen.com/subject/spaeettx.html