4. 生成器(generator)
4.1. 生成器简介
首先请确信,生成器就是一种迭代器。生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中。另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的特性之一。
从Python 2.5开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作。这部分我们会在稍后的部分介绍。
4.2. 生成器函数
4.2.1. 使用生成器函数定义生成器
如何获取一个生成器?首先来看一小段代码:
1 2 3 4 5 6 7 |
>>> def get_0_1_2(): ... yield 0 ... yield 1 ... yield 2 ... >>> get_0_1_2 <function get_0_1_2 at 0x00B2CB70> |
我们定义了一个函数get_0_1_2,并且可以查看到这确实是函数类型。但与一般的函数不同的是,get_0_1_2的函数体内使用了关键字yield,这使得get_0_1_2成为了一个生成器函数。生成器函数的特性如下:
调用生成器函数将返回一个生成器;
1 2 3 |
>>> generator = get_0_1_2() >>> generator <generator object get_0_1_2 at 0x00B1C7D8> |
第一次调用生成器的next方法时,生成器才开始执行生成器函数(而不是构建生成器时),直到遇到yield时暂停执行(挂起),并且yield的参数将作为此次next方法的返回值;
1 2 |
>>> generator.next() 0 |
之后每次调用生成器的next方法,生成器将从上次暂停执行的位置恢复执行生成器函数,直到再次遇到yield时暂停,并且同样的,yield的参数将作为next方法的返回值;
1 2 3 4 |
>>> generator.next() 1 >>> generator.next() 2 |
如果当调用next方法时生成器函数结束(遇到空的return语句或是到达函数体末尾),则这次next方法的调用将抛出StopIteration异常(即for循环的终止条件);
1 2 3 4 |
>>> generator.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的。
我们的小例子中并没有用到变量,所以这里另外定义一个生成器来展示这个特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> def fibonacci(): ... a = b = 1 ... yield a ... yield b ... while True: ... a, b = b, a+b ... yield b ... >>> for num in fibonacci(): ... if num > 100: break ... print num, ... 1 1 2 3 5 8 13 21 34 55 89 |
看到while True可别太吃惊,因为生成器可以挂起,所以是延迟计算的,无限循环并没有关系。这个例子中我们定义了一个生成器用于获取斐波那契数列。
4.2.2. 生成器函数的FAQ
接下来我们来讨论一些关于生成器的有意思的话题。
1. 你的例子里生成器函数都没有参数,那么生成器函数可以带参数吗?
当然可以啊亲,而且它支持函数的所有参数形式。要知道生成器函数也是函数的一种:)
1 2 3 4 5 |
>>> def counter(start=0): ... while True:/span>0): ... while True:代器要简单不少,所以生成器也是最常用到的特性之一。
从Python 2.5开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作。这部分我们会在稍后的部分介绍。 4.2. 生成器函数 4.2.1. 使用生成器函数定义生成器 如何获取一个生成器?首先来看一小段代码:
我们定义了一个函数get_0_1_2,并且可以查看到这确实是函数类型。但与一般的函数不同的是,get_0_1_2的函数体内使用了关键字yield,这使得get_0_1_2成为了一个生成器函数。生成器函数的特性如下: 调用生成器函数将返回一个生成器;
第一次调用生成器的next方法时,生成器才开始执行生成器函数(而不是构建生成器时),直到遇到yield时暂停执行(挂起),并且yield的参数将作为此次next方法的返回值;
之后每次调用生成器的next方法,生成器将从上次暂停执行的位置恢复执行生成器函数,直到再次遇到yield时暂停,并且同样的,yield的参数将作为next方法的返回值;
如果当调用next方法时生成器函数结束(遇到空的return语句或是到达函数体末尾),则这次next方法的调用将抛出StopIteration异常(即for循环的终止条件);
生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的。
看到while True可别太吃惊,因为生成器可以挂起,所以是延迟计算的,无限循环并没有关系。这个例子中我们定义了一个生成器用于获取斐波那契数列。 4.2.2. 生成器函数的FAQ 接下来我们来讨论一些关于生成器的有意思的话题。 1. 你的例子里生成器函数都没有参数,那么生成器函数可以带参数吗? 当然可以啊亲,而且它支持函数的所有参数形式。要知道生成器函数也是函数的一种:)
|