5. 迭代器和生成器

../_images/05_iterator.png

5.1. 迭代器(iterator)

特点:
  • 迭代器是访问集合元素的一种方式,不能随机访问集合中的某个值,只能从头到尾依次访问( next() 方法),访问到一半时不能往回退。

  • 不需要事先准备好整个迭代过程中的所有元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。

  • 便于循环比较大的数据集合,节省内存。

  • 不能复制一个迭代器,如果要再次(或者同时)迭代同一个对象,只能去创建另一个迭代器对象。

1## enumerate 返回迭代器
2a = enumerate(['a','b'])
3
4for i in range(2): ## 迭代两次enumerate对象
5    for x, y in a:
6        print x, y
7    print "========"

结果是:

(0, 'a')
(1, 'b')
========
========

可以发现:第二次返回值为空。

5.1.1. 可迭代对象(iterable)

可以直接作用于 for 循环的对象统称为可迭代对象(Iterable) 。只要定义了可以返回一个迭代器的 __iter__() 方法,或者定义了可以支持下标索引的 __getitem__() 方法,那么它就是一个可迭代对象。

 1class Iterator_test(object):
 2    def __init__(self, data):
 3        self.data = data
 4        self.index = len(data)
 5
 6    def __iter__(self):
 7        return self
 8
 9    def next(self):
10        if self.index <= 0:
11            raise StopIteration
12        self.index -= 1
13        return self.data[self.index]
14
15iterator_winter = Iterator_test('abcde')
16for item in iterator_winter:
17    print item
18## 打印 e d c b a
19
20class Iterator_test2(object):
21    def __init__(self, data):
22        self.data = data
23    def __getitem__(self, it):
24        return self.data[it]
25no_iter = Iterator_test2('abcde')
26for item in no_iter:
27    print item
28## 打印 a b c d e

常见的可迭代对象:

  • 集合数据类型,如 list、tuple、dict、set、str 等。

  • generator,包括生成器和带 yield 的 generator function。

可以被 next() 函数调用并不断返回下一个值的对象称为迭代器(Iterator) 。生成器都是 Iterator 对象,但 list、dict、str 虽然是 Iterable,却不是 Iterator。

所有的 Iterable 均可以通过内置函数 iter() 来转变为 Iterator

判断一个对象是否是可迭代对象使用 isinstance

 1>>> from collections import Iterable
 2>>> a = [1,2,3]
 3>>> isinstance(a, Iterable)
 4True
 5>>> a = iter(a)
 6>>> next(a)
 71
 8>>> next(a)
 92
10>>> next(a)
113
12>>> next(a)
13Traceback (most recent call last):
14  File "<stdin>", line 1, in <module>
15StopIteration

一个可迭代对象是不能独立进行迭代的,Python 中迭代是通过 for ... in 来完成的 。 for循环会不断调用迭代器对象的 __next__() 方法(python3 __next__() ;python2 next() ),每次循环,都返回迭代器对象的下一个值,直到遇到 StopIteration 异常。

任何实现了 __iter__()__next__() (python2 中实现 next() )方法的对象都是迭代器, __iter__() 返回迭代器自身, __next__() 返回容器中的下一个值 。

5.2. 生成器(generator)

生成器其实是一种特殊的迭代器。它不需要再像上面的类一样写 __iter__()__next__() 方法了,只需要一个 yield 关键字。 yield 就是 return 返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。

 1def generator_winter():
 2    i = 1
 3    while i <= 3:
 4        yield i
 5        i += 1
 6
 7generator_iter = generator_winter() ## 函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
 8print generator_iter.next() ## 1
 9print generator_iter.next() ## 2
10print generator_iter.next() ## 3
11print generator_iter.next() ## 抛出 StopIteration 异常

生成器表达式 类似于列表推导式,只是把 [] 换成 ()。

 1gen = (x for x in range(10)) ## <generator object <genexpr> at 0x0000000012BC4990>
 2for item in gen:
 3    print item
 4
 5## fibonacci 数列
 6def fibonacci(n):
 7    a, b = 0, 1
 8    while b <= n:
 9        yield b
10        a, b = b, a+b
11f = fibonacci(10)
12for item in f:
13    print item

5.2.1. send

send 方法可带一个参数,参数可以是 None。None 参数的 send 方法和 next/__next__ 的功能完全相同:将暂停在 yield 语句出的程序继续执行;非 None 参数的 send 方法会将参数值作为 yield 语句返回值赋值给接收者。

注意:非 None 参数的 send 方法无法启动执行生成器函数。也就是说,程序中第一次使用生成器调用 send 方法时,参数只能是 None(推荐直接使用 next/__next__)。

5.2.2. close

当程序在生成器函数中遇到 yield 语句暂停运行时,调用 close 方法会阻止生成器函数继续执行,该函数会在程序停止运行的位置抛出 GeneratorExit 异常。 虽然通过捕获 GeneratorExit 异常,可以继续执行生成器函数中剩余的代码,但这部分代码中不能再包含 yield 语句,否则程序会抛出 RuntimeError 异常。

生成器函数一旦调用 close,后续将无法再通过 next/__next__ 启动生成器,否则会抛出 StopIteration 异常。

5.2.3. throw

在生成器函数执行暂停处,throw 方法抛出一个指定的异常,之后程序会继续执行生成器函数中后续的代码,直到遇到下一个 yield 语句。需要注意的是,如果到剩余代码执行完毕没有遇到下一个 yield 语句,则程序会抛出 StopIteration 异常。

5.2.4. 生产者-消费者模型

生成器中的 yield 可以一定程度上实现协程。

生产者生产消息后,直接通过 yield 跳转到消费者开始执行;待消费者执行完毕后,切换回生产者继续生产,效率极高。

 1import time
 2
 3def consumer():
 4    r = ''
 5    while True:
 6        n = yield r
 7        if not n:
 8            return
 9        print('[CONSUMER] Consuming %s...' % n)
10        time.sleep(1)
11        r = '200 OK'
12
13def produce(c):
14    c.__next__()
15    n = 0
16    while n < 3:
17        n = n + 1
18        print('[PRODUCER] Producing %s...' % n)
19        r = c.send(n)
20        print('[PRODUCER] Consumer return: %s' % r)
21    c.close()
22
23if __name__=='__main__':
24    c = consumer()
25    produce(c)

输出:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK

5.3. 参考资料

  1. Python迭代器,生成器–精华中的精华

  1. python 生成器和迭代器有这篇就够了

  1. Python生成器(send,close,throw)方法详解