迭代器
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_lst
和lst
有区别吗?
>>> 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= 1self.n= 3 ; self.i= 2self.n= 3 ; self.i= 3self.n= 3 ; self.i= 4Traceback (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 fibif __name__ == "__main__": fibs = Fibs(5) print list(fibs) #Python 3: print(list(fibs))
运行结果是:
$ python 21402.py [0, 1, 1, 2, 3, 5]
给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?
以上演示了迭代器的一个具体应用。综合本节上面的内容和前文对迭代的讲述,对迭代器做一个概括:
- 在 Python 中,迭代器是遵循迭代协议的对象。
- 可以使用
iter()
以从任何序列得到迭代器(如 list, tuple, dictionary, set 等)。 - 编写类,实现
__iter__()
方法,以及next()
(Python 2)或__next__()
(Python 3) 。当没有元素时,则引发StopIteration
异常。 - 如果有很多值,列表就会占用太多的内存,而迭代器则占用更少内存。
- 迭代器从第一个元素开始访问,直到所有的元素被访问完结束,只能往前不会后退。
迭代器不仅实用,也很有趣。看下面的操作:
>>> 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)11427>>> for i in my_lst: print i11427
我连续两次调用列表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 i11427>>> for i in my_tup: print i
对于my_tup
,我们已经看到,它是generator对象,关于这个名称先不管它,后面会讲解。当把它用到循环中,它明显是一次性用品,只能使用一次,再次使用,就什么也不显示了。
>>> type(my_lst)<type 'list'>>>> type(my_tup)<type 'generator'>
my_lst
和my_tup
是两种不同的对象,并且my_tup
也不是元组,它是一个generator。其它先不管,请读者在你的Python交互模式中输入dir(my_tup)
,如果是Python 2,请查找是否有__iter__
和next
;如果是Python 3则查看是否有__iter__
和__next__
。答案是肯定的。这也是my_lst
和my_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)
,就要花费时间了,可以尝试一下哦。
迭代器的确有迷人之处,但是它也不是万能之物。比如迭代器不能回退,只能如过河的卒子,不断向前。另外,迭代器也不适合在多线程环境中对可变集合使用(这句话可能理解有困难,先混个脸熟吧,等你遇到多线程问题再说)。