当前位置: 首页 > 科技观察

重新认识Generator

时间:2023-03-21 21:27:15 科技观察

本文转载自微信公众号《小菜学编程》,作者fasionchan。转载本文请联系小彩雪编程公众号。基本用法我们知道函数体中包含yield关键字的函数不是普通函数。这种函数称为生成器(generator),一般用于循环处理结构,应用得当可以大大优化内存使用效率。例如,设计一个打开文件并将每一行转换为大写并返回的函数:defread_file_upper(path):lines=[]withopen(path)asf:forlineinf:lines.append(line.upper())returnlines这个版本的函数内部会创建一个列表对象来保存转换结果。for循环遍历文件的每一行,将其转换为大写并将其附加到列表中。这样,文件中的每一行都需要保存在列表中。如果文件很大,内存开销可想而知。我们可以借助yield关键字将read_file_upper函数变成生成器版本。函数主体逻辑没有变化,只是将每一行数据的处理结果通过yield逐一返回,而不是收集list对象再返回。defiter_file_upper(path):withopen(path)asf:forlineinf:yieldline.upper()如果现在有一个文本文件data.txt,其中包含以下内容:hello,worldlifeisshort,usepythonmywechatidis:coding-fanbye使用iter_file_upper生成器,我们可以这样做处理它:>>>forlineiniter_file_upper('text.txt'):...print(line.strip())HELLO,WORLDLIFEISSHORT,USEPYTHONMYWECHATIDIS:CODING-FANBYEiter_file_upper生成器用法与read_file_upper函数大致相同,但它不会一次Hold住文件的所有数据行,而是逐行处理,逐行返回,这样内存占用最小。行为观察那么,生成器为什么会有如此神奇的效果呢?我们观察一下:>>>g=iter_file_upper('text.txt')>>>g我们调用iter_file_upper后得到一个generator对象,而且不是文件处理的结果,iter_file_upper还没有还没有开始执行。当我们调用next函数从generator接收下一个数据时,iter_file_upper开始执行,停在yield处,将第一行的处理结果返回给我们:>>>next(g)'HELLO,WORLD\n'此时,生成器暂停,如果没有我们的指令,它不会继续处理第二行。当我们再次执行下一个函数时,生成器再次恢复执行,处理下一行数据并在yield处再次暂停:>>>next(g)'LIFEISSHORT,USEPYTHON\n'生成器会记住自己的执行进度,并且每一次调用一次next函数,它总是处理并产生下一个数据,我们根本不需要担心:>>>next(g)'MYWECHATIDIS:CODING-FAN\n'>>>next(g)'BYE\n'当iter_file_upper代码逻辑执行完毕后,会向next抛出异常,通知调用者已经结束:>>>next(g)Traceback(mostrecentcalllast):File"",line1,inStopIteration因此,我们可以简单的认为for-in循环在Python虚拟机中的实现方式如下:不断调用next函数,让生成器产生数据;直到生成器抛出StopIteration异常;在经典的线程模型中,每个线程都有一个独立的执行流程,只能执行一个任务。如果一个程序需要同时处理多个任务,可以使用多进程或多线程技术。假设一个站点需要同时服务多个客户端连接,可以为每个连接创建一个独立的线程进行处理。无论是线程还是进程,切换都会带来巨大的开销:用户态/内核态的切换、执行上下文的保存和恢复、CPU缓存的刷新等。因此,使用线程或进程来驱动执行显然不是一个理想的选择小任务。那么,除了线程和进程之外,还有没有其他的解决方案呢?