11.10 生成器

早先在第8章,我们讨论了迭代器背后的有效性以及它们如何给非序列对象一个像序列的迭代器接口。这很容易明白因为他们仅仅只有一个方法,用于调用获得下个元素的next().

然而,除非你实现了一个迭代器的类,迭代器真正的并没有那么“聪明”。难道调用函数还没有强大到在迭代中以某种方式生成下一个值并且返回和next()调用一样简单的东西?那就是生成器的动机之一。生成器的另外一个方面甚至更加强力——协同程序的概念。协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。在有调用者和(被调用的)协同程序也有通信。举例来说,当协同程序暂停的时候,我们能从其中获得一个中间的返回值,当调用回到程序中时,能够传入额外或者改变了的参数,但仍能够从我们上次离开的地方继续,并且所有状态完整。挂起返回出中间值并多次继续的协同程序被称为生成器,那就是Python的生成器真正在做的事。在2. 2的时候,生成器被加入到Python中接着在2. 3中成为标准(见PEP 255),虽然之前足够强大,但是在Python2. 5的时候,得到了显著的提高(见PEP 342) 。这些提升让生成器更加接近一个完全的协同程序,因为允许值(和异常)能传回到一个继续的函数中。同样地,当等待一个生成器的时候,生成器现在能返回控制。在调用的生成器能挂起(返回一个结果)之前,调用生成器返回一个结果而不是阻塞等待那个结果返回。让我们更进一步观察生成器自顶向下的启动。

什么是Python式的生成器?从语法上讲,生成器是一个带yield语句的函数。一个函数或者子程序只返回一次,但一个生成器能暂停执行并返回一个中间的结果——那就是yield语句的功能,返回一个值给调用者并暂停执行。当生成器的next()方法被调用的时候,它会准确地从离开地方继续(当它返回[一个值以及]控制给调用者时)。

当在2.2生成器被加入的时候,因为它引入了一个新的关键字,yield,为了向下兼容,你需要从future模块中导入generators来使用生成器。从2.3开始,当生成器成为标准的时候,这就不再是必需的了。

11.10.1 简单的生成器特性

与迭代器相似,生成器以另外的方式来运作:当到达一个真正的返回或者函数结束没有更多的值返回(当调用next()),一个Stoplteration异常就会抛出。这里有个例子,简单的生成器:

11.10 生成器 - 图1

现在我们有自己的生成器函数,让我们调用他来获得和保存一个生成器对象(以便我们能调用它的next()方法从这个对象中获得连续的中间值):

11.10 生成器 - 图2

由于Python的for循环有next()调用和对StopIteration的处理,使用一个for循环而不是手动迭代穿过一个生成器(或者那种事物的迭代器)总是要简洁漂亮得多。

11.10 生成器 - 图3

当然这是个有点笨拙的例子:为什么不对这使用真正的迭代器呢?许多动机源自能够迭代穿越序列,而这需要函数威力而不是已经在某个序列中静态对象。

在接下来的例子中,我们将要创建一个带序列并从那个序列中返回一个随机元素的随机迭代器:

11.10 生成器 - 图4

不同点在于每个返回的元素将从那个队列中消失,像一个list.pop()和random.choice()的结合的归类。

11.10 生成器 - 图5

在接下来的几章中,当我们谈到面向对象编程的时候,将看见这个生成器较简单(和无限)的版本作为类的迭代器。在之前的8. 12小节中,我们讨论了生成器表达式的语法。使用这个语法返回的对象是个生成器,但只以一个简单的形式,并允许使用过分简单化的列表解析的语法。

这些简单的例子应该让你有点明白生成器是如何工作的,但你或许会问,“在我的应用中,我可以在哪使用生成器?”或许,你会问”最适合使用这些个强大的构建的地方在哪?”

使用生成器最好的地方就是当你正迭代穿越一个巨大的数据集合,而重复迭代这个数据集合是一个很麻烦的事,比如一个巨大的磁盘文件,或者一个复杂的数据库查询。对于每行的数据,你希望执行非元素的操作以及处理,但当正指向和迭代过它的时候,你“不想失去你的地盘”。

你想要抓取一块数据,比如,将它返回给调用者来处理以及可能的对(另外一个)数据库的插入,接着你想要运行一次next()来获得下一块的数据,等等。状态在挂起和再继续的过程中是保留了的,所以你会觉得很舒服有一个安全的处理数据的环境。没有生成器的话,你的程序代码很有可能会有很长的函数,里面有一个很长的循环。当然,这仅仅是因为一个语言这样的特征不意味着你需要用它。如果在你程序里没有明显适合的话,那就别增加多余的复杂性!当你遇到合适的情况时,你便会知道什么时候生成器正是要使用的东西。

11.10.2 加强的生成器特性

在Python2. 5中,一些加强特性加入到生成器中,所以除了next()来获得下个生成的值,用户可以将值回送给生成器[send()],在生成器中抛出异常,以及要求生成器退出[close()]。

由于双向的动作涉及叫做send()的代码来向生成器发送值(以及生成器返回的值发送回来),现在yield语句必须是一个表达式,因为当回到生成器中继续执行的时候,你或许正在接收一个进入的对象。下面是一个展示了这些特性的,简单的例子。我们用简单的闭包例子,counter:

11.10 生成器 - 图6

生成器带有一个初始化的值,对每次对生成器[next() ]调用以1累加计数。用户已可以选择重置这个值,如果他们非常想要用新的值来调用send()不是调用next()。这个生成器是永远运行的,所以如果你想要终结它,调用close()方法。如果我们交互的运行这段代码,会得到如下输出:

11.10 生成器 - 图7

你可以在PEP 255和PEP 342中,以及为读者介绍Python2. 2中新特性的文章中阅读到更多关于生成器的资料:

http://www. linuxjournal.com/article/5597