X Tutup
>For freedom Christ has set us free. Stand firm, therefore, and do not submit again to a yoke of slavery. >基督释放了我们,叫我们得以自由,所以要站立得稳,不要再被奴仆的轭挟制。(GALATIANS 5:1) #迭代器 迭代,对于读者已经不陌生了,曾有专门一节来讲述,如果印象不深,请复习[《迭代》](./128.md)。 >>> 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 #Python 3返回结果: 从返回结果中可以看出,`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)》](./207md)相关内容。 - `__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 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 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(my_tup) `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)`,就要花费时间了,可以尝试一下哦。 迭代器的确有迷人之处,但是它也不是万能之物。比如迭代器不能回退,只能如过河的卒子,不断向前。另外,迭代器也不适合在多线程环境中对可变集合使用(这句话可能理解有困难,先混个脸熟吧,等你遇到多线程问题再说)。 ------ [总目录](./index.md)   |   [上节:黑魔法](./240.md)   |   [下节:生成器](./215.md) 如果你认为有必要打赏我,请通过支付宝:**qiwsir@126.com**,不胜感激。
X Tutup