购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.2 生成器函数

Python提供了生成器函数工具,该工具具有强大的功能。在生成器函数的众多功能中,最值得关注的是创建迭代器功能。

生成器函数和普通函数非常相似。但是,生成器函数不使用return作为关键字,而是使用新关键字yield。下面是一个简单的例子:

在for循环中,其用法如下:

解释一下,当以调用函数的方式调用gen_nums()时,该函数会返回一个生成器对象:

生成器函数为gen_nums(),即定义并调用的函数。如果函数使用yield而非return,此函数即为生成器函数。在调用生成器函数时,会返回生成器对象。在上面的函数中,sequence就是生成器对象。

牢记要点

生成器函数总是返回生成器对象,不会返回其他对象。

生成器对象是一个迭代器,可以使用next()函数或for循环对它进行迭代:

分析代码的执行流程,当首次调用next(),或for循环刚启动时,函数gen nums()的主体从头开始执行,并返回yield右侧的值。

1.2.1 继续执行next()

首次调用next()函数时,函数从头开始执行,与普通函数的执行过程很相似。但是,当再次调用next()函数,或在for循环中执行下一次迭代时,该函数并不会重新从头执行,而是会从yield语句之后的代码开始执行。再次查看gen_nums()的源代码:

gen_nums()不是普通的函数或子程序,而是协程。通常,普通函数可以有多个退出点(即return语句),但仅有一个入口点,即每次调用普通函数时都从函数体的首行开始执行。

协程在某些方面和函数比较相似,不过协程可能具有多个入口点。和普通函数一样,协程也是从第一行代码开始执行。不过,当协程执行到“返回”操作时,并不会彻底结束程序的运行,而是进入暂停状态。当继续调用next()时,或在for循环中执行下一次迭代时,会接着从暂停的yield语句执行。重新进入的入口点,即yield语句之后的代码行。

这就是协程的关键所在,每个yield语句同时定义了退出点和重新进入点。

对于生成器对象,每当请求新值时,控制流会从yield语句之后的下一行继续执行。在本例中,下一行代码会将变量n的值加1,然后继续执行while循环。

注意,gen_nums()函数体并没有抛出StopIteration异常。当函数体最终结束时,即退出while循环,生成器对象会自动抛出StopIteration异常。

再次强调,每个yield语句都同时定义了退出点和重新进入点。生成器支持包含多个yield语句:

输出如下所示:

退出while循环后,会触发第2个yield。当函数到达结尾的隐式返回操作时,迭代就会停止。仔细研究上述代码,以确保理解其中的逻辑。

1.2.2 转换为生成器函数

再次回顾1.1节中的迭代平方值序列示例。最初的实现方式如下:

作为练习,我们先暂停一下,新建Python文件并打开,然后编写一个gen_squares()生成器函数,实现相同的功能。

完成的代码如下所示:

注意,gen_squares()使用了内置的range()函数,后者返回一个可迭代对象,这一点非常重要。

因为range()返回的是列表,当MAX值非常大时,生成器函数内部会创建一个特别大的列表,严重破坏代码的扩展性。

更重要的一点,生成器函数的扩展性取决于扩展性最差的代码行。生成器函数有可能占用较少的内存,前提是代码编写得够好。在编写生成器函数时,一定要注意隐藏的性能瓶颈。

1.2.3 生成器的必要性

严格地讲,我们并不需要生成器函数进行迭代。需要生成器的原因,是因为使用生成器能实现各种高效扩展的设计模式。

例如,你能在不编写生成器函数的情况下创建迭代器吗?当然可以。比如,要创建平方数列表,可以这样做:

每次调用__next__()方法就能获取值,直到抛出StopIteration异常。输出结果相同,但比较SquaresIterator类源代码与生成器源代码,哪种方法的可读性更强?哪种方法更容易维护?当需求发生变化时,哪种方法更容易修改且不会引入错误?相信大多数人都会认为生成器方法更简洁且更自然。

有的作者经常使用“生成器”指代生成器函数,或者表示调用函数返回的生成器对象。通常,作者会想当然地混用术语。混用有时没问题,但更多的情况下,混用并不妥。对于重要的概念,有时人们也不作很清晰的区分。函数与返回值之间存在很大区别,因此生成器函数和生成器对象也是完全不同的。

建议读者在思考、表达中,尽量使用“生成器函数”和“生成器对象”这两个术语,以保持概念清晰。工作沟通中也要如此,这样能帮助同事更好地理解。但存在一个例外,当你想表达“生成器函数和对象”这一语言特性时,直接说“生成器”也是可以的。在本书中,我将以身作则,严格区分术语。 qtGFA9OIfjuouGTp7PxT9uC54N+RADCJUv8X+uEKsRSrGQ/E2FH4eo3PG+liQvWD

点击中间区域
呼出菜单
上一章
目录
下一章
×