python迭代器资料整理

发布时间:2019-10-12 20:08:33编辑:auto阅读(1551)

    什么是迭代器

    相关概念定义

    迭代器(Iterator):

    满足迭代协议的对象就是迭代器
    iterator就是实现了Iteration Protocol的对象,这类对象都支持循环遍历的操作(for/while/支持迭代的函数list() sum()...)。

    迭代协议(Iteration Protocol):

    内建函数iter()接收一个可迭代对象,并返回一个可迭代对象.
    每次将这个可迭代对象传递给next()函数,都会返回它所包含的下一个元素,当迭代完最后一个元素时,就会触发StopIteration异常。

    >>> x = iter([1, 2, 3])
    >>> x
    <listiterator object at 0x1004ca850>
    >>> next(x)
    1
    >>> next(x)
    2
    >>> next(x)
    3
    >>> next(x)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    满足以上要求的对象,就是迭代器。

    迭代隐含的操作

    在每次的迭代语句中,python都会按照迭代协议去对迭代器进行迭代。其实,在实际执行中,python会进行一些其他的操作:

    1. 将需要迭代的对象作为参数传递给iter函数

    2. iter返回一个迭代器对象

    3. 每次循环则将返回的迭代器对象传递给next函数

    4. 循环至最后一个元素,触发StopIteration

    for语句为例
    当我们在Python中执行循环语句for i in foo的时候,其背后的操作是:

    1. foo = iter(foo)

    2. next(foo)

    next(foo)在python3中执行的是:foo.__next__(),在python2中则是:foo.next()

    迭代器的实现

    迭代器是用class来实现的。其中必需实现的有两个方法:__iter__next(python2)/__next__(python3)。其中,__iter__必需返回一个迭代器对象,next则负责迭代逻辑并在迭代完毕时触发异常。

    如下:

    def Iter(object)
        def __init__(self):
            pass
        def __iter__(self):
            pass
        def __next__(self):    # python3
            pass
        def next(self):        # python2
            pass

    迭代器的特殊情况

    迭代器的__iter__返回self

    迭代器的__iter__方法需要返回的是一个具有next方法的可迭代对象。如果当__iter__返回的是self的话,就会产生其他意想不到的效果。

    class yrange:
        def __init__(self, n):
            self.i = 0
            self.n = n
    
        def __iter__(self):
            return self
    
        def next(self):
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()
                
    class zrange:
        def __init__(self, n):
            self.i = 0
            self.n = n
    
        def __iter__(self):
            return zrange(self.n)
            
        def next(self):
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()

    执行结果:

    >>> y = yrange(5)
    >>> list(y)
    [0, 1, 2, 3, 4]
    >>> list(y)
    []
    >>> z = zrange(5)
    >>> list(z)
    [0, 1, 2, 3, 4]
    >>> list(z)
    [0, 1, 2, 3, 4]

    yrange中,iter返回的是self,在执行list(y)iter返回的都是同一个self,所以再次调用list(y)时只会触发结束迭代异常,列表中并无内容。
    而在zrange中,每次执行list(z)时,iter都是返回一个新的迭代器zrange(self.n),所以每次执行list(z)都得到完整的元素。

    生成器的迭代

    通常,对于数据量特别大的序列,我们会用生成器generator来代替容器对象container,这样可以利用lazy evaluable来节省内存开销。值得注意的是,生成器也是一个只能迭代一次的迭代器。

    def grange(n):
        i = 0
        while i < n:
            yield i
            i += 1

    执行结果:

    >>> glist = grange(10)
    >>> list(glist)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> list(glist)
    []

    如果是利用便捷的生成器表达式也是一样:

    >>> alist = (i for i in range(10))
    >>> list(alist)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> list(alist)
    []

    要解决这个问题,可以将迭代器和生成器组合使用:

    class Grange(object):
        def __init__(self, n):
            self.n = n
        
        def __iter__(self):
            for i in range(self.n):
                yield i

    结果:

    >>> glist = Grange(10)
    >>> list(glist)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> list(glist)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    值得注意的是,平常我们利用到生成器的地方都是数据量特别大的情况,这个时候,其实应该尽量避免多次迭代生成器。我想这应该也是python没有支持对生成器多次迭代的特性的原因。

    编程建议

    在实际的编程中,往往需要在函数中多次迭代一个序列,如果这个序列是调用API得到的,而你又不能保证它是没有陷阱的迭代器时。可以在遍历迭代器的时候,加入一个判断语句,避免无法多次迭代的情况发生:

    def iterator_checker(iterator):
        assert iter(iterator) is not iter(iterator), "iter() return self"

    参考资料

关键字