Python迭代器

释放双眼,带上耳机,听听看~!

迭代器是在Python2.2中被加入的,它为类序列对象提供了一个类序列的接口,有了迭代器可以迭代一个不是序列的对象,因为它表现出了序列的行为。关于Python中的迭代器,有几个比较容易混淆的概念:可迭代对象(iterable)、迭代器(iterator)、生成器(generator),本文将对此做一个详细的讲解。


1、简介

首先,我们用一张图来清晰地展示出几个概念之间的关系
Python迭代器
通过上面这张图,可以得到:

  1. 列表/集合/字典推导式可以得到相应的容器对象
  2. 通常而言,容器对象都是可迭代对象
  3. 给内建函数iter()传入一个可迭代对象,可以得到一个迭代器;迭代器属于可迭代对象
  4. 给内建函数next()传入一个迭代器,可以实现惰性求值
  5. 生成器包括两种形式:生成器表达式和生成器函数;生成器属于迭代器

接下来,我们一一进行解释。

2、可迭代对象和迭代器

首先,我们来了解一下迭代器协议:

  1. 迭代器协议是指:对象必须提供一个next方法,执行方法要么返回迭代器中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往后走,不能往前退)
  2. 可迭代对象:实现了迭代器协议的对象(实现方式:对象内部定义了一个__iter__方法)
  3. 协议是一种约定,可迭代对象实现了迭代器协议,Python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象

简单来说,如果一个对象实现了__iter__方法,其是可迭代对象,它的__iter__方法返回的是当前对象的迭代器类的实例;如果一个对象实现了__iter__和next方法,其是迭代器,它的__iter__方法返回自身,表示自身即是自己的迭代器。我们来看一段代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1# 定义可迭代对象
2class MyIterable(object):
3   def __init__(self, n):
4       self.n = n
5   def __iter__(self):
6       return MyIterator(self.n)
7
8# 定义迭代器:Fibonacci数列
9class MyIterator(object):
10  def __init__(self, n):
11      self.n = n
12      self.num = 0
13      self.pre, self.post = 0, 1
14  def __iter__(self):
15      return self
16  def next(self):
17      if self.num < self.n:
18          self.pre, self.post = self.post, self.pre + self.post
19          self.num += 1
20          return self.pre
21      else:
22          raise StopIteration
23
24obj_1 = MyIterable(10) # 可迭代对象
25obj_2 = MyIterator(10) # 迭代器
26
27obj_1
28>>> <__main__.MyIterable at 0x10ad36e90>
29iter(obj_1) # 迭代器
30>>> <__main__.MyIterator at 0x10addda90>
31obj_2
32>>> <__main__.MyIterator at 0x10ac56ad0>
33iter(obj_2) # 返回自身
34>>> <__main__.MyIterator at 0x10ac56ad0>
35
36for i in obj_1:
37  print i
38
39for i in obj_2:
40  print i
41
42

关于上述代码,这里讲几点:

  • 内建函数iter(),有两种用法,其中一种是iter(collection),作用是返回一个迭代器,本质上它是调用了collection的__iter__方法,并把__iter__的返回结果作为自己的返回值
  • 内建函数next(),本质上是调用了迭代器的next方法,并把next的返回结果作为自己的返回值
  • 当我们用for语句去访问一个可迭代对象时,实际上是先通过内建函数iter()获取了一个迭代器,然后再调用迭代器的next方法去获取值

可以通过isinstance来判断是一个对象是否属于可迭代对象或者迭代器:


1
2
3
4
5
6
7
1from collections import Iterable, Iterator
2isinstance([], Iterable)
3>>> True
4isinstance([], Iterator)
5>>> False
6
7

在迭代可变对象时,一个序列的迭代器只是记录当前到达了序列中的第几个元素,所以如果在迭代过程中改变了序列的元素,更新会立即反应到所迭代的条目上:


1
2
3
4
5
6
7
8
9
10
11
12
1c = [1, 2, 3, 4, 5]
2for i in c:
3   print i
4   c.remove(i)
5# 第一次迭代后,c=[2, 3, 4, 5]
6>>> 1
7# 迭代器取第2个元素,是3,迭代完成后,c=[2, 4, 5]
8>>> 3
9># 迭代器取第3个元素,是5,迭代完成后,c=[2, 4],迭代结束
10>>> 5
11
12

Python内部的数据结构,基本上都是可迭代对象,序列型的如字符串、列表和元组,非序列型的如字典和文件。

使用迭代器,主要有以下优点:a).定义了统一的访问集合的接口,只要实现迭代器协议,就可以使用迭代器进行访问;b).惰性求值,在迭代至当前元素时才计算,省内存。有一点值得注意的是,迭代器不能多次迭代,不过,仅实现了__iter__方法的可迭代对象可以多次迭代。

3、生成器

生成器是一种特殊的迭代器,它自动实现了迭代器协议(即__iter__和next方法)。
Python有两种不同的方式提供生成器:

  1. 生成器函数:常规函数定义,但是,使用yield表达式而不是return语句返回结果;yield表达式一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
  2. 生成器表达式:类似于列表推导式(构建于()内),但是,生成器表达式返回按需产生结果的一个对象,而不是一次构建一个结果列表

来看一个生成器函数的例子:


1
2
3
4
5
6
7
8
9
10
11
12
1def gensquares(N):
2   for i in range(N):
3       yield i ** 2
4
5obj = gensquares(5)
6obj
7>>> <generator object gensquares at 0x10add4640>
8
9for item in obj:
10  print item
11
12

再看一个生成器表达式的例子:


1
2
3
4
5
6
7
8
9
1# 生成器表达式定义于圆括号内
2obj = (i ** 2 for i in range(5))
3obj
4>>> <generator object <genexpr> at 0x10abb78c0>
5
6for item in obj:
7   print item
8
9

通过dir(obj)可以发现,上述两种方式生成的对象obj,均包含__iter__和next方法。

下面这段代码,可以看出生成器函数内部的执行过程:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1def zrange(n):
2   print "beginning of zrange"
3   i = 0
4   while i < n:
5       print "before yield", i
6       yield i
7       i += 1
8       print "after yield", i
9   print "endding of zrange"
10
11obj = zrange(3)
12print obj.next()
13>>> beginning of zrange
14>>> before yield 0
15>>> 0
16print obj.next()
17>>> after yield 1
18>>> before yield 1
19>>> 1
20print obj.next()
21>>> after yield 2
22>>> before yield 2
23>>> 2
24print obj.next()
25>>> after yield 3
26>>> endding of zrange
27>>> ---------------------------------------------------------------------------
28>>> StopIteration                             Traceback (most recent call last)
29>>> <ipython-input-261-fb8e20feb398> in <module>()
30>>> ----> 1 print obj.next()
31>>>
32>>> StopIteration:
33
34

可以看出:

  • 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有执行
  • 当next方法第一次被调用的时候,生成器函数才开始执行,执行到yield处停止
  • next方法的返回值就是yield处的参数(yield somevalue)
  • 当继续调用next方法的时候,函数将接着上一次停止的yield处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常。

生成器还有两个重要的方法:

  1. send(value):我们已经知道,next方法可以恢复生成器状态并继续执行,其实send()是除next外另一个恢复生成器的方法;生成器函数中的yield表达式可以有一个值,而这个值就是send()方法的参数,所以send(None)等价于next,同样,next和send()的返回值都是yield处的参数(yield somevalue);有一点需要注意的是,调用send()传入非None值前,生成器必须处于挂起状态,否则将抛出异常,也就是说,第一次调用时,要使用next或send(None),因为没有yield来接收这个值。
  2. close():这个方法用于关闭生成器,对关闭的生成器再次调用next()或send()将抛出StopIteration异常

看一段代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1def zrange(n):
2   i = 0
3   while i < n:
4       val = yield i
5       print "val is: ", val
6       i += 1
7
8obj = zrange(5)
9print obj.next()
10>>> 0
11print obj.next()
12>>> val is:  None
13>>> 1
14print obj.next("Hello")
15>>> val is:  Hello
16>>> 2
17obj.close()
18print obj.next()
19>>> ---------------------------------------------------------------------------
20>>> StopIteration                             Traceback (most recent call last)
21>>> <ipython-input-29-ae1995e67165> in <module>()
22>>> ----> 1 print obj.next()
23>>>
24>>> StopIteration:
25
26

利用生成器的send()函数,可以实现在迭代过程中修改当前迭代值:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1def zrange(n):
2   i = 0
3   while i < n:
4       val = yield i
5       i = i + 1 if val is None else val
6
7obj = zrange(10)
8print obj.next()
9>>> 0
10print obj.next()
11>>> 1
12print obj.send(5)
13>>> 5
14
15

4、itertools

Python内置的itertools模块包含了一系列用来产生不同类型迭代器的函数或类,它们的返回值都是迭代器,我们可以通过for循环来遍历取值,也可以使用next()来取值。
itertools模块提供的迭代器函数或类有以下几种类型:

  1. 无限迭代器:生成一个无限序列,比如自然数序列 1, 2, 3, 4, …;
  2. 有限迭代器:接收一个或多个序列(sequence)作为参数,进行组合、分组和过滤等;
  3. 组合生成器:序列的排列、组合,求序列的笛卡儿积等;

关于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/
以上为本文的全部参考文献,对原作者表示感谢。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全技术

JVM(java 虚拟机)内存设置

2022-1-11 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索