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= 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的最小的数,能不能在上述代码基础上改造得出呢?
以上演示了迭代器的一个具体应用。综合本节上面的内容和前文对迭代的讲述,对迭代器做一个概括:
- 在 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)
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_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)
,就要花费时间了,可以尝试一下哦。
迭代器的确有迷人之处,但是它也不是万能之物。比如迭代器不能回退,只能如过河的卒子,不断向前。另外,迭代器也不适合在多线程环境中对可变集合使用(这句话可能理解有困难,先混个脸熟吧,等你遇到多线程问题再说)。
如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。