Python学习之旅—生成器与迭代器案例剖析
前言
前面一篇博客笔者带大家详细探讨了生成器与迭代器的本质,本次我们将实际分析一个具体案例来加深对生成器与迭代器相关知识点的理解。
本次的案例是一个文件过滤操作,所做的主要操作就是过滤出一个目录下的文件中含有python的行。我们先直接上代码:
import os def init(func): #预激生成器 装饰器
def wrapper(*args,**kwargs):
g=func(*args,**kwargs) # 这里是一个生成器函数
next(g)
return g
return wrapper @init
def list_files(target): #target为opener_g
while 1:
dir_to_search = yield
for top_dir, dir, files in os.walk(dir_to_search):
for file in files:
target.send(os.path.join(top_dir,file))
@init
def opener(target): # target为cat_g
while 1:
file = yield
fn=open(file, encoding='utf-8')
target.send((file, fn))
@init
def cat(target): #target为grep_g
while 1:
file,fn=yield
for line in fn:
target.send((file,line))
@init
def grep(pattern,target): #target为printer_g
while 1:
file,line=yield
if pattern in line:
target.send(file)
@init
def printer():
while 1:
file=yield
if file:
print(file) g = list_files(opener(cat(grep('python',printer()))))
g.send('D:\pythoncodes\hellobipython')
【001】init(func)函数是一个装饰器函数,在该函数体里面,被它装饰过的函数在执行之前,就已经执行过一次next,也就是被该装饰器装饰的函数会停在第一个yield那里等你,函数需要等待我们给它一个信号(send方法)或者调用next方法,才能继续往下走,该装饰器函数的主要作用就是预激活生成器。
【002】继续看下面的函数:list_files(),该函数是一个生成器函数,只要出现dir_to_search = yield赋值表达式,我们就知道这里肯定调用了send方法,这里我们推测是通过send方法传递了一个路径进来。这里有一个os.walk模块,我们通过实际代码来观察该模块的作用:
"""
查看某一个路径下文件和文件夹的信息
打印如下:
返回的是一个元组,第一项为路径或者是子路径,第二项为路径或者是子路径下的文件夹,第三项为路径或者是子路径下的文件名
('D:\\pythoncodes\\hellobipython', ['.idea'], ['config.py', 'RedisDemo.py', 'RequestDemo.py', 'UrllibDemo.py', 'weixinspider.py'])
('D:\\pythoncodes\\hellobipython\\.idea', [], ['hellobipython.iml', 'misc.xml', 'modules.xml', 'workspace.xml'])
""""""
import os
ret = os.walk(r'D:\pythoncodes\hellobipython') # 保证字符串原样输出,使用r来保证不用转义
print(ret) # <generator object walk at 0x000002B8F53FBE60> for i in ret:
print(i)
【003】我们接着来看代码:for top_dir, dir, files in os.walk(dir_to_search),在这个for循环中,它只取file。接下来看代码:target.send(os.path.join(top_dir,file)),根据前面生成器和迭代器的知识,这句话使用send方法给生成器target传递了一个参数os.path.join(top_dir,file),这个参数的意思是将绝对路径名和文件名拼接起来,组合成为一个绝对路径,然后传递给生成器target。
【004】接着我们来看生成器函数opener(target),同理和上面的分析类似,最后一句target.send((file, fn))相当于给生成器target传递一个文件和该文件的文件句柄。
【005】接下来我们来看生成器函数cat(target),可以看到它通过表达式file,fn=yield接收了文件和该文件的文件句柄,然后获取该文件的每一行,再通过代码target.send((file,line))将该文件和文件的每一行都发送给生成器cat.
【006】接下来我们来看生成器函数grep(target),file,fn=yield接收文件和该文件的文件句柄,接下来的一行代码:if pattern in line,可以猜测该句的意思是某个目标在不在某一行里面,如果pattern在该行,则将该行发送给生成器target.
【007】 最后再来观察最后一个生成器函数printer(),函数体执行的主要内容就是接收一个文件file,只要该文件不为空,那么就将该文件打印出来。
以上就是各个函数体的内容,接下来我们就来看看具体的函数加载和调用顺序。
【01】Python解释器从上到下执行当读取到第一个@init时相当于执行了如下的语句:list_files = init(list_files),当执行init(list_files)时,就执行了
init函数,此时返回了内部函数名:wrapper。此时这里wrapper里面的func就是list_files。
【02】同理,类似于上面的分析,下面依次会执行opener= init(opener),cat= init(cat).....依次类推,这里是装饰器里面的知识。
【03】在执行完上面的步骤后,接着往下面走:g = list_files(opener(cat(grep('python',printer()))));前面我们说过对于这种嵌套的函数,我们的执行标准就是从里往外面执行,因此这里首先执行printer()。
【04】接着上面的继续分析,执行printer,本质上执行的是wrapper,这是装饰器里面的知识。因为执行printer()语句时,上面的@init已经执行了表达式printer= init(printer),此时printer返回一个wrapper,我们在执行printer()时,就相当于执行wrapper(),此时wrapper里面的func()相当于生成器函数printer(),执行wrapper()里面的内容,相当于就拿到了生成器g,而且对该生成器进行了激活,这就是所谓的预激活生成器。
【05】在执行完毕next(g)后,相当于执行到printer()函数里面的yield表达式:file=yield;注意yield后面没有任何值,因此返回的也是空,而在我们的wrapper函数里面也没有接收返回值,只是执行了一个next(g),接着返回了一个生成器对象g。该生成器对象相当于是执行完printer()语句后的返回值,我们暂且称之为:printer_g。这里只是为了标识,我们只需要知道printer_g是一个生成器函数执行后返回的一个生成器对象,而且该对象已经被激活,根据前面的知识,已经被激活表示此时生成器函数printer()已经执行到第一个yield表达式这里:file=yield,此时就在这里等着,等着谁来给它send一个参数,然后将该参数的值赋值给file。
【06】紧接着我们来执行grep('python',printer()),相当于执行grep('python',printer_g),此时pattern就相当于python,参数target的值就相当于生成器对象printer_g。同样和上面一样,执行grep()相当于执行wrapper(),只是此时wrapper中的func()相当于生成器函数grep(),那么此时我们又拿到了生成器对象grep_g,因为只要执行grep('python',printer()),我们其实就拿到了生成器对象grep_g,只不过该生成器对象在装饰器中被预激活了。那么grep()函数的函数体就执行到表达式file,line=yield这里等待着grep_g通过send方法传递两个参数进来。此时执行完grep函数后,又返回了一个被激活的生成器,和上面一样,我们称之为grep_g。
【07】接着我们来执行cat函数,和上面的分析一样,执行完毕后,我们拿到了一个被激活的生成器:cat_g。
【08】接着我们来执行opener()函数,返回的同样是一个被激活的生成器:open_g,而且opener()函数也执行到函数体里面的yield表达式这里。
【09】最后执行list_files()函数,和上面一样,也返回一个被激活的生成器list_files_g。因此最终表达式变为:g = list_files_g
【10】执行g.send('D:\pythoncodes\hellobipython'),相当于将参数D:\pythoncodes\hellobipython传递给生成器list_files_g,也相当于
传递给表达式dir_to_search = yield左边的dir_to_search,此时dir_to_search = 'D:\pythoncodes\hellobipython'。
【11】接着我们继续执行dir_to_search = yield下面的内容,当执行完毕target.send(os.path.join(top_dir, file))后,此时target为生成器
opener_g。
【12】我们前面说过opener函数在file=yield这里停着,你现在给我发送了一个完整的文件路径,然后赋值给file。在opener()函数中,我们拿到了
文件句柄,并将文件路径和文件句柄又一起发送给了cat_g。注意,这里文件路径和文件句柄组成了一个元组。
【13】此时cat函数接收到文件路径和文件句柄,然后在cat函数中,又继续send给生成器grep_g,这里发送的是文件和文件的每一行,以元组的形式发送。
【14】接下来继续从grep_g函数中的表达式file,line=yield开始执行,然后判断pattern是否在line中,根据我们前面传递过来的参数,pattern为python,然后执行
target.send(file),将文件路径发送给生成器函数printer()函数,然后从printer()函数体中的file=yield开始执行,只要文件存在,那么就将该文件打印出来。
总结:
综合上面的分析,该程序完成的功能就是:一个路径下的所有文件,只要你这个文件中有python关键字,就将该文件打印出来。
思考:为什么要写这样的程序,我们其实可以写一个程序就可以解决。
1.在程序中,我们肯定要反复地打开和关闭文件,这样就要反复地创建变量;如果使用生成器解决,那么就不会出现这样的问题。生成器会直接保存相应的状态,
每个函数都是单独执行,你执行你的,我执行我的。
2.如果我们自己开发程序,我们首先要拿到文件,这样,我们每拿到一个文件,就要去调用另一个函数来判断这个文件是否含有关键词,然后再遍历下一个文件。
现在我使用生成器,我直接将文件发送给你,然后我来控制你做,这样就完成了一定的协同工作。即函数与函数之间的调用是依赖于某些条件的。
3.我们需要明白的是,协程函数的精髓在于它从原来一根弦地执行程序变成了在函数内部通过条件控制函数是否继续执行。也就是说,正常情况下,我们要保证函数执行完毕后然后返回给你,现在我来控制函数在哪停,在哪执行。直到我又找到了符合条件的file;比如我们这里的打印函数printer()和判断函数grep(),你打印你的,我判断我的,大家互不干扰,这就体现了协程的概念。
结语:
本次总结的内容比较难于理解,主要考察生成器和装饰器的结合使用,希望大家能够好好理解这个程序,真正掌握生成器的使用。
Python学习之旅—生成器与迭代器案例剖析的更多相关文章
- Python学习之旅—生成器对象的send方法详解
前言 在上一篇博客中,笔者带大家一起探讨了生成器与迭代器的本质原理和使用,本次博客将重点聚焦于生成器对象的send方法. 一.send方法详解 我们知道生成器对象本质上是一个迭代器.但是它比迭代器对 ...
- Python学习笔记之生成器、迭代器和装饰器
这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonnic 哦 1.生成器 什么是生成器呢?简单来说,在 Python 中一边循环一边计算的机制称为 ...
- python 学习2:生成器,迭代器,装饰器
1.生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万 个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那 ...
- Python学习日记(十) 生成器和迭代器
使用dir()我们可以知道这个数据类型的内置函数有什么方法: print(dir(int)) print(dir(bool)) print(dir([])) print(dir({})) print( ...
- 180分钟的python学习之旅
最近在很多地方都可以看到Python的身影,尤其在人工智能等科学领域,其丰富的科学计算等方面类库无比强大.很多身边的哥们也提到Python非常的简洁方便,比如用Django搭建一个见得网站只需要半天时 ...
- python学习之旅
python学习分类 python基础 +- day01——python初始.变量.常量.注释.基础数据类型.输入.if day02——while.字符串格式化.运算符.编码初识 day03—— ...
- Python基础(冒泡、生成器、迭代器、列表与字典解析)
一.冒泡算法 冒泡算法,给定一组数据,从大到小排序或者从小到大排序,就像气泡一样 原理: 相邻的两个对象相比,大的放到后面,交换位置 交换位置通过a,b=b,a来实现 1.我们可以通过for循环来根 ...
- python初步学习-生成式、生成器、迭代器、装饰器
生成式 列表生成式 字典生成式 集合生成式 嵌套列表生成式 列表生成式 列表生成式是python受欢迎的语法之一,通过一句简洁的语法就可以对一组元素进行过滤,还可以对得到的元素进行转换处理.语法格式为 ...
- python学习笔记(8)迭代器和生成器
迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退 ...
随机推荐
- Go -- 如何使用gcore工具获取一个core文件而不重启应用?
问题: 当调试一个程序的时候,理想状态是不重启应用程序就获取core文件. 解决: gcore命令可以使用下面步骤来获取core文件: 1. 确认gdb软件包已经被正确安装. 2. 使用调试参数编译程 ...
- 【paddle学习】图像分类
https://zhuanlan.zhihu.com/p/28871960 深度学习模型中的卷积神经网络(Convolution Neural Network, CNN)近年来在图像领域取得了惊人的成 ...
- 【试水CAS-4.0.3】第06节_CAS服务端配置HTTPS
完整版见https://jadyer.github.io/2012/05/30/tomcat-https/ /** * @see CAS服务端配置HTTPS * @see -------------- ...
- Android——ListView优化
1.ListView基本概念 列表显示需要三个元素: ListView:用来展示列表的View. 适配器:用来把数据映射到ListView上 数据:具体的将被映射的字符串,图片或基本组件 适配器类型分 ...
- 关于Java中强制类型转换的问题
为了更好的理解我们先看下面的例子: package com.yonyou.test; import java.util.ArrayList; import java.util.Iterator; im ...
- c程序设计语言第一章5
练习1.20请编写程序d e t a b
- MFC Month Calendar Control 控件使用
在上层软件编程中,往往须要提供一个月历控件让用户选择对应日期或者用此月历控件来强调特定的一天. MFC的 Month Calendar Control 控件自系统升级到 Windows 7 之后,对于 ...
- [游戏]L4D求生之路官方比赛地图修补完好说明
游戏模式:L4D求生之路4356(1.0.2.1)药抗比赛模式 更新日期:2015.06.04 -----毫不留情01----- 1.开局补给手枪 -----毫不留情02----- 1.开局补给手枪 ...
- 2016-1-8 windows 7下安装mysql及其配置和运用
绪言 最近学习了一下mysql的相关用法,以及vs2010结合mysql的使用. 遇到的问题:1.安装mysql 5.6 绿色免安装版本,出现mysql server not connect loca ...
- Arcgis Engine(ae)接口详解(5):IGeometry几何基础操作
//点操作~~~~~~~~~~~~~~~~~~~~~~~~~ //通过坐标生成点 IPoint point = new PointClass(); point.PutCoords(, ); //获取点 ...