《编写高质量Python代码的59个有效方法》——第16条:考虑用生成器来改写直接返回列表的函数

第16条:考虑用生成器来改写直接返回列表的函数
如果函数要产生一系列结果,那么最简单的做法就是把这些结果都放在一份列表里,并将其返回给调用者。例如,我们要查出字符串中每个词的首字母,在整个字符串里的位置。下面这段代码,用append方法将这些词的首字母索引添加到result列表中,并在函数结束时将其返回给调用者。

输入一些范例值,以验证该函数能够正常运作:

index_words函数有两个问题。
第一个问题是,这段代码写得有点拥挤。每次找到新的结果,都要调用append方法。但我们真正应该强调的,并不是对result.append方法的调用,而是该方法给列表中添加的那个值,也就是index + 1。另外,函数首尾还各有一行代码用来创建及返回result列表。于是,在函数主体部分的约130个字符(不计空白字符)里,重要的大概只有75个。
这个函数改用生成器(generator)来写会更好。生成器是使用yield表达式的函数。调用生成器函数时,它并不会真的运行,而是会返回迭代器。每次在这个迭代器上面调用内置的next函数时,迭代器会把生成器推进到下一个yield表达式那里。生成器传给yield的每一个值,都会由迭代器返回给调用者。
下面的这个生成器函数,会产生和刚才那个函数相同的效果。

这个函数不需要包含与result列表相交互的那些代码,因而看起来比刚才那种写法清晰许多。原来那个result列表中的元素,现在都分别传给yield表达式了。调用该生成器后所返回的迭代器,可以传给内置的list函数,以将其转换为列表(相关的原理可参见本书第9条)。

index_words的第二个问题是,它在返回前,要先把所有结果都放在列表里面。如果输入量非常大,那么程序就有可能耗尽内存并崩溃。相反,用生成器改写后的版本,则可以应对任意长度的输入数据。
下面定义的这个生成器,会从文件里面依次读入各行内容,然后逐个处理每行中的单词,并产生相应结果。该函数执行时所耗的内存,由单行输入值的最大字符数来界定。

运行这个生成器函数,也能产生和原来相同的效果。

定义这种生成器函数的时候,唯一需要留意的就是:函数返回的那个迭代器,是有状态的,调用者不应该反复使用它(参见本书第17条)。
要点
使用生成器比把收集到的结果放入列表里返回给调用者更加清晰。
由生成器函数所返回的那个迭代器,可以把生成器函数体中,传给yield表达式的那些值,逐次产生出来。
无论输入量有多大,生成器都能产生一系列输出,因为这些输入量和输出量,都不会影响它在执行时所耗的内存。

时间: 2024-12-03 10:52:55

《编写高质量Python代码的59个有效方法》——第16条:考虑用生成器来改写直接返回列表的函数的相关文章

《编写高质量Python代码的59个有效方法》——导读

目 录 前 言 致 谢第1章 用Pythonic方式来思考 第1条:确认自己所用的Python版本 第2条:遵循PEP 8风格指南 第3条:了解bytes.str与unicode的区别第4条:用辅助函数来取代复杂的表达式第5条:了解切割序列的办法第6条:在单次切片操作内,不要同时指定start.end和stride第7条:用列表推导来取代map和f?ilter 第8条:不要使用含有两个以上表达式的列表推导第9条:用生成器表达式来改写数据量较大的列表推导第10条:尽量用enumerate取代ran

《编写高质量Python代码的59个有效方法》——第3条:了解bytes、str与unicode的区别

第3条:了解bytes.str与unicode的区别 Python 3有两种表示字符序列的类型:bytes和str.前者的实例包含原始的8位值:后者的实例包含Unicode字符. Python 2也有两种表示字符序列的类型,分别叫做str和unicode.与Python 3不同的是,str的实例包含原始的8位值:而unicode的实例,则包含Unicode字符. 把Unicode字符表示为二进制数据(也就是原始8位值)有许多种办法.最常见的编码方式就是UTF-8.但是大家要记住,Python 3

《编写高质量Python代码的59个有效方法》——第2条:遵循PEP 8风格指南

第2条:遵循PEP 8风格指南 <Python Enhancement Proposal #8>(8号Python增强提案)又叫PEP 8,它是针对Python代码格式而编订的风格指南.尽管可以在保证语法正确的前提下随意编写Python代码,但是,采用一致的风格来书写可以令代码更加易懂.更加易读.采用和其他Python程序员相同的风格来写代码,也可以使项目更利于多人协作.即便代码只会由你自己阅读,遵循这套风格也依然可以令后续的修改变得容易一些. PEP 8列出了许多细节,以描述如何撰写清晰的P

《编写高质量Python代码的59个有效方法》——第17条:在参数上面迭代时,要多加小心

第17条:在参数上面迭代时,要多加小心如果函数接受的参数是个对象列表,那么很有可能要在这个列表上面多次迭代.例如,要分析来美国Texas旅游的人数.假设数据集是由每个城市的游客数量构成的(单位是每年百万人).现在要统计来每个城市旅游的人数,占总游客数的百分比.为此,需要编写标准化函数(normalization function).它会把所有的输入值加总,以求出每年的游客总数.然后,用每个城市的游客数除以总数,以求出该城市所占的比例. 把各城市的游客数量放在一份列表里,传给该函数,可以得到正确结

《编写高质量Python代码的59个有效方法》——第20条:用None和文档字符串来描述具有动态默认值的参数

第20条:用None和文档字符串来描述具有动态默认值的参数有时我们想采用一种非静态的类型,来做关键字参数的默认值.例如,在打印日志消息的时候,要把相关事件的记录时间也标注在这条消息中.默认情况下,消息里面所包含的时间,应该是调用log函数那一刻的时间.如果我们以为参数的默认值会在每次执行函数时得到评估,那可能就会写出下面这种代码. 两条消息的时间戳(timestamp)是一样的,这是因为datetime.now只执行了一次,也就是它只在函数定义的时候执行了一次.参数的默认值,会在每个模块加载进来

《编写高质量Python代码的59个有效方法》——第9条:用生成器表达式来改写数据量较大的列表推导

第9条:用生成器表达式来改写数据量较大的列表推导 列表推导(参见本书第7条)的缺点是:在推导过程中,对于输入序列中的每个值来说,可能都要创建仅含一项元素的全新列表.当输入的数据比较少时,不会出问题,但如果输入的数据非常多,那么可能会消耗大量内存,并导致程序崩溃. 例如,要读取一份文件并返回每行的字符数.若采用列表推导来做,则需把文件每一行的长度都保存在内存中.如果这个文件特别大,或是通过无休止的network socket(网络套接字)来读取,那么这种列表推导就会出问题.下面的这段列表推导代码,

《编写高质量Python代码的59个有效方法》——第18条:用数量可变的位置参数减少视觉杂讯

第18条:用数量可变的位置参数减少视觉杂讯 令函数接受可选的位置参数(由于这种参数习惯上写为*args,所以又称为star args,星号参数),能够使代码更加清晰,并能减少视觉杂讯(visual noise). 例如,要定义log函数,以便把某些调试信息打印出来.假如该函数的参数个数固定不变,那它就必须接受一段信息及一份含有待打印值的列表. 即便没有值要打印,只想打印一条消息,调用者也必须像上面那样,手工传入一份空列表.这种写法既麻烦,又显得杂乱.最好是能令调用者把第二个参数完全省略掉.若想在

《编写高质量Python代码的59个有效方法》——第8条:不要使用含有两个以上表达式的列表推导

第8条:不要使用含有两个以上表达式的列表推导除了基本的用法(参见本书第7条)之外,列表推导也支持多重循环.例如,要把矩阵(也就是包含列表的列表,即二维列表)简化成一维列表,使原来的每个单元格都成为新列表中的普通元素.这个功能采用包含两个for表达式的列表推导即可实现,这些for表达式会按照从左至右的顺序来评估. 上面这个例子简单易懂,这就是多重循环的合理用法.还有一种包含多重循环的合理用法,那就是根据输入列表来创建有两层深度的新列表.例如,我们要对二维矩阵中的每个单元格取平方,然后用这些平方值构

《编写高质量Python代码的59个有效方法》——第15条:了解如何在闭包里使用外围作用域中的变量

第15条:了解如何在闭包里使用外围作用域中的变量 假如有一份列表,其中的元素都是数字,现在要对其排序,但排序时,要把出现在某个群组内的数字,放在群组外的那些数字之前.这种用法在绘制用户界面时候可能会遇到,我们可以用这个办法把重要的消息或意外的事件优先显示在其他内容前面. 实现该功能的一种常见做法,是在调用列表的sort方法时,把辅助函数传给key参数.这个辅助函数的返回值,将会用来确定列表中各元素的顺序.辅助函数可以判断受测元素是否处在重要群组中,并据此返回相应的排序关键字(sort key).