迭代器是在Python2.2中被加入的,它为类序列对象提供了一个类序列的接口,有了迭代器可以迭代一个不是序列的对象,因为它表现出了序列的行为。关于Python中的迭代器,有几个比较容易混淆的概念:可迭代对象(iterable)、迭代器(iterator)、生成器(generator),本文将对此做一个详细的讲解。
1、简介
首先,我们用一张图来清晰地展示出几个概念之间的关系
通过上面这张图,可以得到:
- 列表/集合/字典推导式可以得到相应的容器对象
- 通常而言,容器对象都是可迭代对象
- 给内建函数
iter()
传入一个可迭代对象,可以得到一个迭代器;迭代器属于可迭代对象
- 给内建函数
next()
传入一个迭代器,可以实现惰性求值
- 生成器包括两种形式:生成器表达式和生成器函数;生成器属于迭代器
接下来,我们一一进行解释。
2、可迭代对象和迭代器
首先,我们来了解一下迭代器协议:
- 迭代器协议是指:对象必须提供一个
next
方法,执行方法要么返回迭代器中的下一项,要么就引起一个StopIteration
异常,以终止迭代(只能往后走,不能往前退)
- 可迭代对象:实现了迭代器协议的对象(实现方式:对象内部定义了一个
__iter__
方法)
- 协议是一种约定,可迭代对象实现了迭代器协议,Python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象
简单来说,如果一个对象实现了__iter__
方法,其是可迭代对象,它的__iter__
方法返回的是当前对象的迭代器类的实例;如果一个对象实现了__iter__
和next
方法,其是迭代器,它的__iter__
方法返回自身,表示自身即是自己的迭代器。我们来看一段代码:
# 定义可迭代对象
class MyIterable(object):
def __init__(self, n):
self.n = n
def __iter__(self):
return MyIterator(self.n)
# 定义迭代器:Fibonacci数列
class MyIterator(object):
def __init__(self, n):
self.n = n
self.num = 0
self.pre, self.post = 0, 1
def __iter__(self):
return self
def next(self):
if self.num < self.n:
self.pre, self.post = self.post, self.pre + self.post
self.num += 1
return self.pre
else:
raise StopIteration
obj_1 = MyIterable(10) # 可迭代对象
obj_2 = MyIterator(10) # 迭代器
obj_1
>>> <__main__.MyIterable at 0x10ad36e90>
iter(obj_1) # 迭代器
>>> <__main__.MyIterator at 0x10addda90>
obj_2
>>> <__main__.MyIterator at 0x10ac56ad0>
iter(obj_2) # 返回自身
>>> <__main__.MyIterator at 0x10ac56ad0>
for i in obj_1:
print i
for i in obj_2:
print i
关于上述代码,这里讲几点:
- 内建函数
iter()
,有两种用法,其中一种是iter(collection)
,作用是返回一个迭代器,本质上它是调用了collection的__iter__
方法,并把__iter__
的返回结果作为自己的返回值
- 内建函数
next()
,本质上是调用了迭代器的next
方法,并把next
的返回结果作为自己的返回值
- 当我们用for语句去访问一个可迭代对象时,实际上是先通过内建函数
iter()
获取了一个迭代器,然后再调用迭代器的next
方法去获取值
可以通过isinstance来判断是一个对象是否属于可迭代对象或者迭代器:
from collections import Iterable, Iterator
isinstance([], Iterable)
>>> True
isinstance([], Iterator)
>>> False
在迭代可变对象时,一个序列的迭代器只是记录当前到达了序列中的第几个元素,所以如果在迭代过程中改变了序列的元素,更新会立即反应到所迭代的条目上:
c = [1, 2, 3, 4, 5]
for i in c:
print i
c.remove(i)
# 第一次迭代后,c=[2, 3, 4, 5]
>>> 1
# 迭代器取第2个元素,是3,迭代完成后,c=[2, 4, 5]
>>> 3
># 迭代器取第3个元素,是5,迭代完成后,c=[2, 4],迭代结束
>>> 5
Python内部的数据结构,基本上都是可迭代对象,序列型的如字符串、列表和元组,非序列型的如字典和文件。
使用迭代器,主要有以下优点:a).定义了统一的访问集合的接口,只要实现迭代器协议,就可以使用迭代器进行访问;b).惰性求值,在迭代至当前元素时才计算,省内存。有一点值得注意的是,迭代器不能多次迭代,不过,仅实现了__iter__
方法的可迭代对象可以多次迭代。
3、生成器
生成器是一种特殊的迭代器,它自动实现了迭代器协议(即__iter__
和next
方法)。
Python有两种不同的方式提供生成器:
- 生成器函数:常规函数定义,但是,使用yield表达式而不是return语句返回结果;yield表达式一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
- 生成器表达式:类似于列表推导式(构建于
()
内),但是,生成器表达式返回按需产生结果的一个对象,而不是一次构建一个结果列表
来看一个生成器函数的例子:
def gensquares(N):
for i in range(N):
yield i ** 2
obj = gensquares(5)
obj
>>> <generator object gensquares at 0x10add4640>
for item in obj:
print item
再看一个生成器表达式的例子:
# 生成器表达式定义于圆括号内
obj = (i ** 2 for i in range(5))
obj
>>> <generator object <genexpr> at 0x10abb78c0>
for item in obj:
print item
通过dir(obj)可以发现,上述两种方式生成的对象obj,均包含__iter__
和next
方法。
下面这段代码,可以看出生成器函数内部的执行过程:
def zrange(n):
print "beginning of zrange"
i = 0
while i < n:
print "before yield", i
yield i
i += 1
print "after yield", i
print "endding of zrange"
obj = zrange(3)
print obj.next()
>>> beginning of zrange
>>> before yield 0
>>> 0
print obj.next()
>>> after yield 1
>>> before yield 1
>>> 1
print obj.next()
>>> after yield 2
>>> before yield 2
>>> 2
print obj.next()
>>> after yield 3
>>> endding of zrange
>>> ---------------------------------------------------------------------------
>>> StopIteration Traceback (most recent call last)
>>> <ipython-input-261-fb8e20feb398> in <module>()
>>> ----> 1 print obj.next()
>>>
>>> StopIteration:
可以看出:
- 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有执行
- 当
next
方法第一次被调用的时候,生成器函数才开始执行,执行到yield处停止
-
next
方法的返回值就是yield处的参数(yield somevalue)
- 当继续调用
next
方法的时候,函数将接着上一次停止的yield处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常。
生成器还有两个重要的方法:
- send(value):我们已经知道,
next
方法可以恢复生成器状态并继续执行,其实send()是除next
外另一个恢复生成器的方法;生成器函数中的yield表达式可以有一个值,而这个值就是send()方法的参数,所以send(None)等价于next
,同样,next
和send()的返回值都是yield处的参数(yield somevalue);有一点需要注意的是,调用send()传入非None值前,生成器必须处于挂起状态,否则将抛出异常,也就是说,第一次调用时,要使用next
或send(None),因为没有yield来接收这个值。
- close():这个方法用于关闭生成器,对关闭的生成器再次调用next()或send()将抛出StopIteration异常
看一段代码:
def zrange(n):
i = 0
while i < n:
val = yield i
print "val is: ", val
i += 1
obj = zrange(5)
print obj.next()
>>> 0
print obj.next()
>>> val is: None
>>> 1
print obj.next("Hello")
>>> val is: Hello
>>> 2
obj.close()
print obj.next()
>>> ---------------------------------------------------------------------------
>>> StopIteration Traceback (most recent call last)
>>> <ipython-input-29-ae1995e67165> in <module>()
>>> ----> 1 print obj.next()
>>>
>>> StopIteration:
利用生成器的send()函数,可以实现在迭代过程中修改当前迭代值:
def zrange(n):
i = 0
while i < n:
val = yield i
i = i + 1 if val is None else val
obj = zrange(10)
print obj.next()
>>> 0
print obj.next()
>>> 1
print obj.send(5)
>>> 5
4、itertools
Python内置的itertools模块包含了一系列用来产生不同类型迭代器的函数或类,它们的返回值都是迭代器,我们可以通过for循环来遍历取值,也可以使用next()来取值。
itertools模块提供的迭代器函数或类有以下几种类型:
- 无限迭代器:生成一个无限序列,比如自然数序列 1, 2, 3, 4, …;
- 有限迭代器:接收一个或多个序列(sequence)作为参数,进行组合、分组和过滤等;
- 组合生成器:序列的排列、组合,求序列的笛卡儿积等;
关于itertools的具体使用,可以参考这篇文章,这里不细述。
参考文献
[1] https://nvie.com/posts/iterators-vs-generators/
[2] https://foofish.net/iterators-vs-generators.html
[3] https://blog.csdn.net/Jmilk/article/details/52560255
[4] https://blog.csdn.net/jinixin/article/details/72232604
[5] https://www.zhihu.com/question/20829330
[6] https://www.jb51.net/article/73939.htm
[7] http://blog.51cto.com/11026142/1858860
[8] https://blog.csdn.net/u010159842/article/details/53896620
[9] http://python.jobbole.com/85240/
[10] http://funhacks.net/2017/02/13/itertools/
以上为本文的全部参考文献,对原作者表示感谢。