For freedom Christ has set us free. Stand firm, therefore, and do not submit again to a yoke of slavery.

基督释放了我们,叫我们得以自由,所以要站立得稳,不要再被奴仆的轭挟制。(GALATIANS 5:1)

迭代器

迭代,对于读者已经不陌生了,曾有专门一节来讲述,如果印象不深,请复习《迭代》

>>> hasattr(list, '__iter__')
True

不仅仅是列表,文件、字典都有一个名为__iter__的方法,这说明它们都是可迭代的。

__iter__()是对象的一个特殊方法,它是迭代规则(iterator potocol)的基础,有了它,说明对象是可迭代的。

跟迭代有关的一个内建函数iter(),它的文档中这样描述:

>>> help(iter)
Help on built-in function iter in module __builtin__:

iter(...)
    iter(collection) -> iterator
    iter(callable, sentinel) -> iterator

    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.

这个函数前文介绍过,它返回一个迭代器对象。比如:

>>> lst = [1, 2, 3, 4]
>>> iter_lst = iter(lst)
>>> iter_lst
<listiterator object at 0x02BE8D50>        #Python 3返回结果:<list_iterator object at 0x00000000034CD6D8>

从返回结果中可以看出,iter_lst引用的是迭代器对象。那么,iter_lstlst有区别吗?

>>> hasattr(lst, "__iter__")
True
>>> hasattr(iter_lst, "__iter__")
True

它们都有__iter__,这是相同点,说明它们都是可迭代的。

但是:

Python 2:

>>> hasattr(lst, "next")
False
>>> hasattr(iter_lst, "next")
True

Python 3:

>>> hasattr(lst, "__next__")
False
>>> hasattr(iter_lst, "__next__")
True

这就是两者的区别。我们像iter_lst所引用的对象那样,具有next()(Python 2)或者__next__()(Python 3)方法的对象,称之为迭代器对象。显见,迭代器对象必然是可迭代的,反之则不然。

Python 3中迭代器对象实现的是__next__()方法,不是next()。并且,在Python 3中有一个内建函数next(),可以实现next(it)访问迭代器,这相当于于Python 2中的it.next()(it是迭代对象)。

为了体现Python强悍,自己写一个迭代器对象。

#!/usr/bin/env python
# coding=utf-8

"""
the interator as range()
"""
class MyRange(object):    #Python 3: class MyRange:
    def __init__(self, n):
        self.i = 1
        self.n = n

    def __iter__(self):
        return self

    def next(self):        #Python 3: def __next__(self):
        if self.i <= self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

if __name__ == "__main__":
    x = MyRange(7)
    print [i for i in x]        #Python 3中使用print()函数,下同,从略

将代码保存,并运行,结果是:

[1, 2, 3, 4, 5, 6, 7]

以上代码的含义,是自己仿写了类似range()的类,但是跟range()又有所不同,除了结果不同之外,还有:

  • MyRange的初始化方法__init__()就不用赘述了,因为前面已经非常详细分析了这个方法,如果复习,请阅读《类(2)》相关内容。

  • __iter__()是类中的核心,它返回了迭代器本身。一个实现了__iter__()方法的对象,即意味着它是可迭代的。

  • 实现next()或者__next__()方法,从而使得这个对象是迭代器对象,并且方法中判断,在不满足条件的时候要发起StopIteration()异常。

再来看range()(以下仅仅限于Python 2):

>>> a = range(7)
>>> hasattr(a, "__iter__")
True
>>> hasattr(a, "next") 
False
>>> print a
[0, 1, 2, 3, 4, 5, 6]

所以我写的类和range()还是有很大区别的。

为了能深入理解迭代器的工作过程,我们这样来操作:

if __name__ == "__main__":
    x = MyRange(3)
    print "self.n=",x.n,";","self.i=",x.i    #Python 3中使用print()函数,下同,从略
    x.next()
    print "self.n=",x.n,";","self.i=",x.i
    x.next()
    print "self.n=",x.n,";","self.i=",x.i
    x.next()
    print "self.n=",x.n,";","self.i=",x.i
    x.next()
    print "self.n=",x.n,";","self.i=",x.i

运行结果如下:

self.n= 3 ; self.i= 1
self.n= 3 ; self.i= 2
self.n= 3 ; self.i= 3
self.n= 3 ; self.i= 4

Traceback (most recent call last):
  File "F:\MyGitHub\StarterLearningPython\2code\21401.py", line 32, in <module>
    x.next()
  File "F:\MyGitHub\StarterLearningPython\2code\21401.py", line 21, in next
    raise StopIteration()
StopIteration

next()或者__next__()中的self.i <= self.n为假,就raise StopIteration(),结束迭代过程。

还记得斐波那契数列吗?前文已经多次用到,这里我们再次使用它,不过是要用它来做一个迭代器对象。

#!/usr/bin/env python
# coding=utf-8
"""
compute Fibonacci by iterator
"""

class Fibs(object):        #Python 3: class Fibs:
    def __init__(self, max):
        self.max = max
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def next(self):        #Python 3: def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

if __name__ == "__main__":
    fibs = Fibs(5)
    print list(fibs)        #Python 3: print(list(fibs))

运行结果是:

$ python 21402.py 
[0, 1, 1, 2, 3, 5]

给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?

以上演示了迭代器的一个具体应用。综合本节上面的内容和前文对迭代的讲述,对迭代器做一个概括:

  1. 在 Python 中,迭代器是遵循迭代协议的对象。
  2. 可以使用iter() 以从任何序列得到迭代器(如 list, tuple, dictionary, set 等)。
  3. 编写类,实现__iter__()方法,以及 next()(Python 2)或__next__()(Python 3) 。当没有元素时,则引发 StopIteration异常。
  4. 如果有很多值,列表就会占用太多的内存,而迭代器则占用更少内存。
  5. 迭代器从第一个元素开始访问,直到所有的元素被访问完结束,只能往前不会后退。

迭代器不仅实用,也很有趣。看下面的操作:

>>> my_lst = [x**x for x in range(4)]
>>> my_lst
[1, 1, 4, 27]
>>> for i in my_lst: print i    #Python 3: print(i)

1
1
4
27
>>> for i in my_lst: print i

1
1
4
27

我连续两次调用列表my_lst进行循环,都能正常进行。这个列表相当于一个耐用品,可以反复使用。

在Python中,除了列表解析式,还可以做元组解析式,方法非常简单:

>>> my_tup = (x**x for x in range(4))
>>> my_tup
<generator object <genexpr> at 0x02B7C2B0>
>>> for i in my_tup: print i

1
1
4
27
>>> for i in my_tup: print i

对于my_tup,我们已经看到,它是generator对象,关于这个名称先不管它,后面会讲解。当把它用到循环中,它明显是一次性用品,只能使用一次,再次使用,就什么也不显示了。

>>> type(my_lst)
<type 'list'>
>>> type(my_tup)
<type 'generator'>

my_lstmy_tup是两种不同的对象,并且my_tup也不是元组,它是一个generator。其它先不管,请读者在你的Python交互模式中输入dir(my_tup),如果是Python 2,请查找是否有__iter__next;如果是Python 3则查看是否有__iter____next__。答案是肯定的。这也是my_lstmy_tup所引用对象的区别。

因此,my_tup引用的是一个迭代器对象。它的next()或者__next__()方法,使得它只能向前。

关于列表和迭代器之间的区别,还有两个非常典型的内建函数:range()xrange(),研究一下这两个的差异,会有所收获的。

range()的结果是一个列表。但是,如果用help(xrange)查看(仅限于Python 2):

class xrange(object)
 |  xrange(stop) -> xrange object
 |  xrange(start, stop[, step]) -> xrange object
 |  
 |  Like range(), but instead of returning a list, returns an object that
 |  generates the numbers in the range on demand.  For looping, this is 
 |  slightly faster than range() and more memory efficient.

xrange()类似range(),但返回的不是列表。在循环的时候,它跟range()相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法:

>>> dir(xrange)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

看到令人兴奋的__iter__了吗?说明它是可迭代的,它返回的是一个可迭代的对象。

也就是说,通过range()得到的列表,会一次性被读入内存,而xrange()返回的对象,则是需要一个数值才从返回一个数值。

上述论述仅适用于Python 2,因为在Python 3里面,将range()优化了,相当于Python 2里面xrange(),所以,在Python 3中就不再有xrange()

还记得zip()吗?

>>> a = ["name", "age"]
>>> b = ["qiwsir", 40]
>>> zip(a,b)
[('name', 'qiwsir'), ('age', 40)]

如果两个列表的个数不一样,就会以短的为准了,比如:

>>> zip(range(4), xrange(100000000))    #适用于Python 2,Python 3中的range()已经具有了Python 2的xrange()功能
[(0, 0), (1, 1), (2, 2), (3, 3)]

第一个range(4)产生的列表被读入内存;第二个是不是也太长了?但是不用担心,它根本不会产生那么长的列表,因为只需要前4个数值,它就提供前四个数值。如果你要修改为range(100000000),就要花费时间了,可以尝试一下哦。

迭代器的确有迷人之处,但是它也不是万能之物。比如迭代器不能回退,只能如过河的卒子,不断向前。另外,迭代器也不适合在多线程环境中对可变集合使用(这句话可能理解有困难,先混个脸熟吧,等你遇到多线程问题再说)。


总目录   |   上节:黑魔法   |   下节:生成器

如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。

results matching ""

    No results matching ""